Merge branch 'main' into dev/support-paint-vertical

This commit is contained in:
Noisyfox
2025-02-23 15:50:21 +08:00
committed by GitHub
344 changed files with 29098 additions and 10940 deletions
+1 -1
View File
@@ -337,7 +337,7 @@ public:
TEdge *p_edge = edges.data();
i = 0;
for (const Path &pg : paths_provider) {
if (num_edges[i]) {
if (num_edges[i] && !pg.empty()) {
bool res = AddPathInternal(pg, num_edges[i] - 1, PolyTyp, Closed, p_edge);
if (res) {
p_edge += num_edges[i];
+5 -6
View File
@@ -114,11 +114,6 @@ void AppConfig::set_defaults()
set_bool("background_processing", false);
#endif
#ifdef SUPPORT_SHOW_DROP_PROJECT
if (get("show_drop_project_dialog").empty())
set_bool("show_drop_project_dialog", true);
#endif
if (get("drop_project_action").empty())
set_bool("drop_project_action", true);
@@ -347,7 +342,11 @@ void AppConfig::set_defaults()
if (get("mouse_wheel").empty()) {
set("mouse_wheel", "0");
}
if (get(SETTING_PROJECT_LOAD_BEHAVIOUR).empty()) {
set(SETTING_PROJECT_LOAD_BEHAVIOUR, OPTION_PROJECT_LOAD_BEHAVIOUR_ASK_WHEN_RELEVANT);
}
if (get("max_recent_count").empty()) {
set("max_recent_count", "18");
}
+6
View File
@@ -18,6 +18,12 @@ using namespace nlohmann;
#define ENV_PRE_HOST "2"
#define ENV_PRODUCT_HOST "3"
#define SETTING_PROJECT_LOAD_BEHAVIOUR "project_load_behaviour"
#define OPTION_PROJECT_LOAD_BEHAVIOUR_LOAD_ALL "load_all"
#define OPTION_PROJECT_LOAD_BEHAVIOUR_ASK_WHEN_RELEVANT "ask_when_relevant"
#define OPTION_PROJECT_LOAD_BEHAVIOUR_ALWAYS_ASK "always_ask"
#define OPTION_PROJECT_LOAD_BEHAVIOUR_LOAD_GEOMETRY "load_geometry_only"
#define SUPPORT_DARK_MODE
//#define _MSW_DARK_MODE
+60 -21
View File
@@ -806,8 +806,8 @@ double configBrimWidthByVolumeGroups(double adhesion, double maxSpeed, const std
// Generate ears
// Ported from SuperSlicer: https://github.com/supermerill/SuperSlicer/blob/45d0532845b63cd5cefe7de7dc4ef0e0ed7e030a/src/libslic3r/Brim.cpp#L1116
static ExPolygons make_brim_ears(ExPolygons& obj_expoly, coord_t size_ear, coord_t ear_detection_length,
coordf_t brim_ears_max_angle, bool is_outer_brim) {
static ExPolygons make_brim_ears_auto(const ExPolygons& obj_expoly, coord_t size_ear, coord_t ear_detection_length,
coordf_t brim_ears_max_angle, bool is_outer_brim) {
ExPolygons mouse_ears_ex;
if (size_ear <= 0) {
return mouse_ears_ex;
@@ -815,7 +815,7 @@ static ExPolygons make_brim_ears(ExPolygons& obj_expoly, coord_t size_ear, coord
// Detect places to put ears
const coordf_t angle_threshold = (180 - brim_ears_max_angle) * PI / 180.0;
Points pt_ears;
for (ExPolygon &poly : obj_expoly) {
for (const ExPolygon &poly : obj_expoly) {
Polygon decimated_polygon = poly.contour;
if (ear_detection_length > 0) {
// decimate polygon
@@ -835,8 +835,8 @@ static ExPolygons make_brim_ears(ExPolygons& obj_expoly, coord_t size_ear, coord
// Then add ears
// create ear pattern
Polygon point_round;
for (size_t i = 0; i < POLY_SIDES; i++) {
double angle = (2.0 * PI * i) / POLY_SIDES;
for (size_t i = 0; i < POLY_SIDE_COUNT; i++) {
double angle = (2.0 * PI * i) / POLY_SIDE_COUNT;
point_round.points.emplace_back(size_ear * cos(angle), size_ear * sin(angle));
}
@@ -850,6 +850,41 @@ static ExPolygons make_brim_ears(ExPolygons& obj_expoly, coord_t size_ear, coord
return mouse_ears_ex;
}
static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWidth, float brim_offset, Flow &flow, bool is_outer_brim)
{
ExPolygons mouse_ears_ex;
BrimPoints brim_ear_points = object->model_object()->brim_points;
if (brim_ear_points.size() <= 0) {
return mouse_ears_ex;
}
const Geometry::Transformation& trsf = object->model_object()->instances[0]->get_transformation();
Transform3d model_trsf = trsf.get_matrix_no_offset();
const Point &center_offset = object->center_offset();
model_trsf = model_trsf.pretranslate(Vec3d(- unscale<double>(center_offset.x()), - unscale<double>(center_offset.y()), 0));
for (auto &pt : brim_ear_points) {
Vec3f world_pos = pt.transform(trsf.get_matrix());
if ( world_pos.z() > 0) continue;
Polygon point_round;
float brim_width = floor(scale_(pt.head_front_radius) / flowWidth / 2) * flowWidth * 2;
if (is_outer_brim) {
double flowWidthScale = flowWidth / SCALING_FACTOR;
brim_width = floor(brim_width / flowWidthScale / 2) * flowWidthScale * 2;
}
coord_t size_ear = (brim_width - brim_offset - flow.scaled_spacing());
for (size_t i = 0; i < POLY_SIDE_COUNT; i++) {
double angle = (2.0 * PI * i) / POLY_SIDE_COUNT;
point_round.points.emplace_back(size_ear * cos(angle), size_ear * sin(angle));
}
mouse_ears_ex.emplace_back();
mouse_ears_ex.back().contour = point_round;
Vec3f pos = pt.transform(model_trsf);
int32_t pt_x = scale_(pos.x());
int32_t pt_y = scale_(pos.y());
mouse_ears_ex.back().contour.translate(Point(pt_x, pt_y));
}
return mouse_ears_ex;
}
//BBS: create all brims
static ExPolygons outer_inner_brim_area(const Print& print,
const float no_brim_offset, std::map<ObjectID, ExPolygons>& brimAreaMap,
@@ -888,9 +923,10 @@ static ExPolygons outer_inner_brim_area(const Print& print,
const float scaled_additional_brim_width = scale_(floor(5 / flowWidth / 2) * flowWidth * 2);
const float scaled_half_min_adh_length = scale_(1.1);
bool has_brim_auto = object->config().brim_type == btAutoBrim;
const bool use_brim_ears = object->config().brim_type == btEar;
const bool has_inner_brim = brim_type == btInnerOnly || brim_type == btOuterAndInner || use_brim_ears;
const bool has_outer_brim = brim_type == btOuterOnly || brim_type == btOuterAndInner || brim_type == btAutoBrim || use_brim_ears;
const bool use_auto_brim_ears = object->config().brim_type == btEar;
const bool use_brim_ears = object->config().brim_type == btPainted;
const bool has_inner_brim = brim_type == btInnerOnly || brim_type == btOuterAndInner || use_auto_brim_ears || use_brim_ears;
const bool has_outer_brim = brim_type == btOuterOnly || brim_type == btOuterAndInner || brim_type == btAutoBrim || use_auto_brim_ears || use_brim_ears;
coord_t ear_detection_length = scale_(object->config().brim_ears_detection_length.value);
coordf_t brim_ears_max_angle = object->config().brim_ears_max_angle.value;
@@ -951,27 +987,30 @@ static ExPolygons outer_inner_brim_area(const Print& print,
if (has_outer_brim) {
// BBS: inner and outer boundary are offset from the same polygon incase of round off error.
auto innerExpoly = offset_ex(ex_poly.contour, brim_offset, jtRound, SCALED_RESOLUTION);
auto &clipExpoly = innerExpoly;
ExPolygons outerExpoly;
if (use_brim_ears) {
outerExpoly = make_brim_ears(object, flowWidth, brim_offset, flow, true);
//outerExpoly = offset_ex(outerExpoly, brim_width_mod, jtRound, SCALED_RESOLUTION);
} else if (use_auto_brim_ears) {
coord_t size_ear = (brim_width_mod - brim_offset - flow.scaled_spacing());
append(brim_area_object, diff_ex(make_brim_ears(innerExpoly, size_ear, ear_detection_length, brim_ears_max_angle, true), clipExpoly));
} else {
// Normal brims
append(brim_area_object, diff_ex(offset_ex(innerExpoly, brim_width_mod, jtRound, SCALED_RESOLUTION), clipExpoly));
outerExpoly = make_brim_ears_auto(innerExpoly, size_ear, ear_detection_length, brim_ears_max_angle, true);
}else {
outerExpoly = offset_ex(innerExpoly, brim_width_mod, jtRound, SCALED_RESOLUTION);
}
append(brim_area_object, diff_ex(outerExpoly, innerExpoly));
}
if (has_inner_brim) {
auto outerExpoly = offset_ex(ex_poly_holes_reversed, -brim_offset);
auto clipExpoly = offset_ex(ex_poly_holes_reversed, -brim_width - brim_offset);
ExPolygons outerExpoly;
auto innerExpoly = offset_ex(ex_poly_holes_reversed, -brim_width - brim_offset);
if (use_brim_ears) {
outerExpoly = make_brim_ears(object, flowWidth, brim_offset, flow, false);
} else if (use_auto_brim_ears) {
coord_t size_ear = (brim_width - brim_offset - flow.scaled_spacing());
append(brim_area_object, diff_ex(make_brim_ears(outerExpoly, size_ear, ear_detection_length, brim_ears_max_angle, false), clipExpoly));
} else {
// Normal brims
append(brim_area_object, diff_ex(outerExpoly, clipExpoly));
outerExpoly = make_brim_ears_auto(offset_ex(ex_poly_holes_reversed, -brim_offset), size_ear, ear_detection_length, brim_ears_max_angle, false);
}else {
outerExpoly = offset_ex(ex_poly_holes_reversed, -brim_offset);
}
append(brim_area_object, diff_ex(outerExpoly, innerExpoly));
}
if (!has_inner_brim) {
// BBS: brim should be apart from holes
+63
View File
@@ -0,0 +1,63 @@
#ifndef BRIMEARSPOINT_HPP
#define BRIMEARSPOINT_HPP
#include <libslic3r/Point.hpp>
namespace Slic3r {
// An enum to keep track of where the current points on the ModelObject came from.
enum class PointsStatus {
NoPoints, // No points were generated so far.
Generating, // The autogeneration algorithm triggered, but not yet finished.
AutoGenerated, // Points were autogenerated (i.e. copied from the backend).
UserModified // User has done some edits.
};
struct BrimPoint
{
Vec3f pos;
float head_front_radius;
BrimPoint()
: pos(Vec3f::Zero()), head_front_radius(0.f)
{}
BrimPoint(float pos_x,
float pos_y,
float pos_z,
float head_radius)
: pos(pos_x, pos_y, pos_z)
, head_front_radius(head_radius)
{}
BrimPoint(Vec3f position, float head_radius)
: pos(position)
, head_front_radius(head_radius)
{}
Vec3f transform(const Transform3d &trsf)
{
Vec3d result = trsf * pos.cast<double>();
return result.cast<float>();
}
bool operator==(const BrimPoint &sp) const
{
float rdiff = std::abs(head_front_radius - sp.head_front_radius);
return (pos == sp.pos) && rdiff < float(EPSILON);
}
bool operator!=(const BrimPoint &sp) const { return !(sp == (*this)); }
template<class Archive> void serialize(Archive &ar)
{
ar(pos, head_front_radius);
}
};
using BrimPoints = std::vector<BrimPoint>;
}
#endif // BRIMEARSPOINT_HPP
+1
View File
@@ -43,6 +43,7 @@ set(lisbslic3r_sources
FaceDetector.hpp
Brim.cpp
Brim.hpp
BrimEarsPoint.hpp
BuildVolume.cpp
BuildVolume.hpp
Circle.cpp
+9
View File
@@ -185,6 +185,15 @@ bool overlaps(const ExPolygons& expolys1, const ExPolygons& expolys2)
return false;
}
bool overlaps(const ExPolygons& expolys, const ExPolygon& expoly)
{
for (const ExPolygon& el : expolys) {
if (el.overlaps(expoly))
return true;
}
return false;
}
Point projection_onto(const ExPolygons& polygons, const Point& from)
{
Point projected_pt;
+1
View File
@@ -457,6 +457,7 @@ inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double toleranc
bool expolygons_match(const ExPolygon &l, const ExPolygon &r);
bool overlaps(const ExPolygons& expolys1, const ExPolygons& expolys2);
bool overlaps(const ExPolygons& expolys, const ExPolygon& expoly);
Point projection_onto(const ExPolygons& expolys, const Point& pt);
+20 -1
View File
@@ -64,6 +64,10 @@ struct SurfaceFillParams
float top_surface_speed = 0;
float solid_infill_speed = 0;
// Params for lattice infill angles
float lattice_angle_1 = 0.f;
float lattice_angle_2 = 0.f;
bool operator<(const SurfaceFillParams &rhs) const {
#define RETURN_COMPARE_NON_EQUAL(KEY) if (this->KEY < rhs.KEY) return true; if (this->KEY > rhs.KEY) return false;
#define RETURN_COMPARE_NON_EQUAL_TYPED(TYPE, KEY) if (TYPE(this->KEY) < TYPE(rhs.KEY)) return true; if (TYPE(this->KEY) > TYPE(rhs.KEY)) return false;
@@ -90,6 +94,8 @@ struct SurfaceFillParams
RETURN_COMPARE_NON_EQUAL(sparse_infill_speed);
RETURN_COMPARE_NON_EQUAL(top_surface_speed);
RETURN_COMPARE_NON_EQUAL(solid_infill_speed);
RETURN_COMPARE_NON_EQUAL(lattice_angle_1);
RETURN_COMPARE_NON_EQUAL(lattice_angle_2);
return false;
}
@@ -111,7 +117,9 @@ struct SurfaceFillParams
this->extrusion_role == rhs.extrusion_role &&
this->sparse_infill_speed == rhs.sparse_infill_speed &&
this->top_surface_speed == rhs.top_surface_speed &&
this->solid_infill_speed == rhs.solid_infill_speed;
this->solid_infill_speed == rhs.solid_infill_speed &&
this->lattice_angle_1 == rhs.lattice_angle_1 &&
this->lattice_angle_2 == rhs.lattice_angle_2;
}
};
@@ -611,6 +619,8 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
params.extruder = layerm.region().extruder(extrusion_role);
params.pattern = region_config.sparse_infill_pattern.value;
params.density = float(region_config.sparse_infill_density);
params.lattice_angle_1 = region_config.lattice_angle_1;
params.lattice_angle_2 = region_config.lattice_angle_2;
if (surface.is_solid()) {
params.density = 100.f;
@@ -953,6 +963,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
params.resolution = resolution;
params.use_arachne = surface_fill.params.pattern == ipConcentric || surface_fill.params.pattern == ipConcentricInternal;
params.layer_height = layerm->layer()->height;
params.lattice_angle_1 = surface_fill.params.lattice_angle_1;
params.lattice_angle_2 = surface_fill.params.lattice_angle_2;
// BBS
params.flow = surface_fill.params.flow;
@@ -972,6 +984,10 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
params.density = layerm->region().config().bridge_density.get_abs_value(1.0);
params.dont_adjust = true;
}
if(surface_fill.surface.is_internal_bridge()){
params.density = f->print_object_config->internal_bridge_density.get_abs_value(1.0);
params.dont_adjust = true;
}
// BBS: make fill
f->fill_surface_extrusion(&surface_fill.surface,
params,
@@ -1022,6 +1038,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
case ipMonotonicLine:
case ipAlignedRectilinear:
case ipGrid:
case ip2DLattice:
case ipTriangles:
case ipStars:
case ipCubic:
@@ -1076,6 +1093,8 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
params.resolution = resolution;
params.use_arachne = false;
params.layer_height = layerm.layer()->height;
params.lattice_angle_1 = surface_fill.params.lattice_angle_1;
params.lattice_angle_2 = surface_fill.params.lattice_angle_2;
for (ExPolygon &expoly : surface_fill.expolygons) {
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
+1
View File
@@ -47,6 +47,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
case ipMonotonic: return new FillMonotonic();
case ipLine: return new FillLine();
case ipGrid: return new FillGrid();
case ip2DLattice: return new Fill2DLattice();
case ipTriangles: return new FillTriangles();
case ipStars: return new FillStars();
case ipCubic: return new FillCubic();
+4
View File
@@ -69,6 +69,10 @@ struct FillParams
// Layer height for Concentric infill with Arachne.
coordf_t layer_height { 0.f };
// For 2D lattice
coordf_t lattice_angle_1 { 0.f };
coordf_t lattice_angle_2 { 0.f };
// BBS
Flow flow;
ExtrusionRole extrusion_role{ ExtrusionRole(0) };
+17
View File
@@ -3002,6 +3002,23 @@ Polylines FillGrid::fill_surface(const Surface *surface, const FillParams &param
return polylines_out;
}
Polylines Fill2DLattice::fill_surface(const Surface *surface, const FillParams &params)
{
Polylines polylines_out;
coordf_t dx1 = tan(Geometry::deg2rad(params.lattice_angle_1)) * z;
coordf_t dx2 = tan(Geometry::deg2rad(params.lattice_angle_2)) * z;
if (! this->fill_surface_by_multilines(
surface, params,
{ { float(M_PI / 2.), float(dx1) }, { float(M_PI / 2.), float(dx2) } },
polylines_out))
BOOST_LOG_TRIVIAL(error) << "Fill2DLattice::fill_surface() failed to fill a region.";
if (this->layer_id % 2 == 1)
for (int i = 0; i < polylines_out.size(); i++)
std::reverse(polylines_out[i].begin(), polylines_out[i].end());
return polylines_out;
}
Polylines FillTriangles::fill_surface(const Surface *surface, const FillParams &params)
{
Polylines polylines_out;
+12
View File
@@ -71,6 +71,18 @@ protected:
float _layer_angle(size_t idx) const override { return 0.f; }
};
class Fill2DLattice : public FillRectilinear
{
public:
Fill* clone() const override { return new Fill2DLattice(*this); }
~Fill2DLattice() override = default;
Polylines fill_surface(const Surface *surface, const FillParams &params) override;
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
float _layer_angle(size_t idx) const override { return 0.f; }
};
class FillTriangles : public FillRectilinear
{
public:
+128 -4
View File
@@ -170,6 +170,7 @@ const std::string BBS_MODEL_CONFIG_RELS_FILE = "Metadata/_rels/model_settings.co
const std::string SLICE_INFO_CONFIG_FILE = "Metadata/slice_info.config";
const std::string BBS_LAYER_HEIGHTS_PROFILE_FILE = "Metadata/layer_heights_profile.txt";
const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/layer_config_ranges.xml";
const std::string BRIM_EAR_POINTS_FILE = "Metadata/brim_ear_points.txt";
/*const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt";
const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt";*/
const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/custom_gcode_per_layer.xml";
@@ -807,10 +808,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
//typedef std::map<Id, ComponentsList> IdToAliasesMap;
typedef std::vector<Instance> InstancesList;
typedef std::map<int, ObjectMetadata> IdToMetadataMap;
typedef std::map<int, CutObjectInfo> IdToCutObjectInfoMap;
//typedef std::map<Id, Geometry> IdToGeometryMap;
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap;
typedef std::map<int, CutObjectInfo> IdToCutObjectInfoMap;
typedef std::map<int, BrimPoints> IdToBrimPointsMap;
/*typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap;
typedef std::map<int, std::vector<sla::DrainHole>> IdToSlaDrainHolesMap;*/
using PathToEmbossShapeFileMap = std::map<std::string, std::shared_ptr<std::string>>;
@@ -1000,9 +1002,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
//IdToGeometryMap m_orig_geometries; // backup & restore
CurrentConfig m_curr_config;
IdToMetadataMap m_objects_metadata;
IdToCutObjectInfoMap m_cut_object_infos;
IdToCutObjectInfoMap m_cut_object_infos;
IdToLayerHeightsProfileMap m_layer_heights_profiles;
IdToLayerConfigRangesMap m_layer_config_ranges;
IdToBrimPointsMap m_brim_ear_points;
/*IdToSlaSupportPointsMap m_sla_support_points;
IdToSlaDrainHolesMap m_sla_drain_holes;*/
PathToEmbossShapeFileMap m_path_to_emboss_shape_files;
@@ -1064,11 +1067,12 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
bool _extract_xml_from_archive(mz_zip_archive& archive, std::string const & path, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler);
bool _extract_xml_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler);
bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_cut_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
void _extract_cut_information_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, ConfigSubstitutionContext &config_substitutions);
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_brim_ear_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
@@ -1270,6 +1274,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
m_objects_metadata.clear();
m_layer_heights_profiles.clear();
m_layer_config_ranges.clear();
m_brim_ear_points.clear();
//m_sla_support_points.clear();
m_curr_metadata_name.clear();
m_curr_characters.clear();
@@ -1759,6 +1764,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
// extract slic3r layer config ranges file
_extract_layer_config_ranges_from_archive(archive, stat, config_substitutions);
}
else if (boost::algorithm::iequals(name, BRIM_EAR_POINTS_FILE)) {
// extract slic3r config file
_extract_brim_ear_points_from_archive(archive, stat);
}
//BBS: disable SLA related files currently
/*else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) {
// extract sla support points file
@@ -1942,6 +1951,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
if (obj_layer_config_ranges != m_layer_config_ranges.end())
model_object->layer_config_ranges = std::move(obj_layer_config_ranges->second);
IdToBrimPointsMap::iterator obj_brim_points = m_brim_ear_points.find(object.second + 1);
if (obj_brim_points != m_brim_ear_points.end())
model_object->brim_points = std::move(obj_brim_points->second);
// m_sla_support_points are indexed by a 1 based model object index.
/*IdToSlaSupportPointsMap::iterator obj_sla_support_points = m_sla_support_points.find(object.second + 1);
if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) {
@@ -2762,6 +2775,77 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
}
}
}
void _BBS_3MF_Importer::_extract_brim_ear_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
{
if (stat.m_uncomp_size > 0) {
std::string buffer((size_t)stat.m_uncomp_size, 0);
mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
if (res == 0) {
add_error("Error while reading brim ear points data to buffer");
return;
}
if (buffer.back() == '\n')
buffer.pop_back();
std::vector<std::string> objects;
boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off);
// Info on format versioning - see bbs_3mf.hpp
int version = 0;
std::string key("brim_points_format_version=");
if (!objects.empty() && objects[0].find(key) != std::string::npos) {
objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string
version = std::stoi(objects[0]);
objects.erase(objects.begin()); // pop the header
}
for (const std::string& object : objects) {
std::vector<std::string> object_data;
boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off);
if (object_data.size() != 2) {
add_error("Error while reading object data");
continue;
}
std::vector<std::string> object_data_id;
boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off);
if (object_data_id.size() != 2) {
add_error("Error while reading object id");
continue;
}
int object_id = std::atoi(object_data_id[1].c_str());
if (object_id == 0) {
add_error("Found invalid object id");
continue;
}
IdToBrimPointsMap::iterator object_item = m_brim_ear_points.find(object_id);
if (object_item != m_brim_ear_points.end()) {
add_error("Found duplicated brim ear points");
continue;
}
std::vector<std::string> object_data_points;
boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off);
std::vector<BrimPoint> brim_ear_points;
if (version == 0) {
for (unsigned int i=0; i<object_data_points.size(); i+=4)
brim_ear_points.emplace_back(float(std::atof(object_data_points[i+0].c_str())),
float(std::atof(object_data_points[i+1].c_str())),
float(std::atof(object_data_points[i+2].c_str())),
float(std::atof(object_data_points[i+3].c_str())));
}
if (!brim_ear_points.empty())
m_brim_ear_points.insert({ object_id, brim_ear_points });
}
}
}
/*
void _BBS_3MF_Importer::_extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
{
@@ -5504,9 +5588,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
//BBS: change volume to seperate objects
bool _add_mesh_to_object_stream(std::function<bool(std::string &, bool)> const &flush, ObjectData const &object_data) const;
bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) const;
bool _add_cut_information_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_brim_ear_points_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config);
@@ -5515,6 +5599,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
//BBS: add project embedded preset files
bool _add_project_embedded_presets_to_archive(mz_zip_archive& archive, Model& model, std::vector<Preset*> project_presets);
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, int export_plate_idx = -1, bool save_gcode = true, bool use_loaded_id = false);
bool _add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model);
bool _add_slice_info_config_file_to_archive(mz_zip_archive &archive, const Model &model, PlateDataPtrs &plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config);
bool _add_gcode_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, Export3mfProgressFn proFn = nullptr);
bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config);
@@ -5914,6 +5999,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
return false;
}
if (!_add_brim_ear_points_file_to_archive(archive, model)) {
close_zip_writer(&archive);
return false;
}
// BBS progress point
/*BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format("export 3mf EXPORT_STAGE_ADD_SUPPORT\n");
if (proFn) {
@@ -7159,6 +7249,40 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
return true;
}
bool _BBS_3MF_Exporter::_add_brim_ear_points_file_to_archive(mz_zip_archive& archive, Model& model)
{
std::string out = "";
char buffer[1024];
unsigned int count = 0;
for (const ModelObject* object : model.objects) {
++count;
const BrimPoints& brim_points = object->brim_points;
if (!brim_points.empty()) {
sprintf(buffer, "object_id=%d|", count);
out += buffer;
// Store the layer height profile as a single space separated list.
for (size_t i = 0; i < brim_points.size(); ++i) {
sprintf(buffer, (i==0 ? "%f %f %f %f" : " %f %f %f %f"), brim_points[i].pos(0), brim_points[i].pos(1), brim_points[i].pos(2), brim_points[i].head_front_radius);
out += buffer;
}
out += "\n";
}
}
if (!out.empty()) {
// Adds version header at the beginning:
out = std::string("brim_points_format_version=") + std::to_string(brim_points_format_version) + std::string("\n") + out;
if (!mz_zip_writer_add_mem(&archive, BRIM_EAR_POINTS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
add_error("Unable to add brim ear points file to archive");
return false;
}
}
return true;
}
/*
bool _BBS_3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model)
{
+4
View File
@@ -141,6 +141,10 @@ inline bool operator & (SaveStrategy & lhs, SaveStrategy rhs)
return ((static_cast<T>(lhs) & static_cast<T>(rhs))) == static_cast<T>(rhs);
}
enum {
brim_points_format_version = 0
};
enum class LoadStrategy
{
Default = 0,
+39 -12
View File
@@ -1838,6 +1838,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_max_layer_z = 0.f;
m_last_width = 0.f;
m_is_overhang_fan_on = false;
m_is_internal_bridge_fan_on = false;
m_is_supp_interface_fan_on = false;
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
m_last_mm3_per_mm = 0.;
@@ -5320,11 +5321,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
ref_speed = std::min(ref_speed, m_config.scarf_joint_speed.get_abs_value(ref_speed));
}
ConfigOptionPercents overhang_overlap_levels({75, 50, 25, 13, 12.99, 0});
ConfigOptionPercents overhang_overlap_levels({90, 75, 50, 25, 13, 0});
if (m_config.slowdown_for_curled_perimeters){
ConfigOptionFloatsOrPercents dynamic_overhang_speeds(
{(m_config.get_abs_value("overhang_1_4_speed", ref_speed) < 0.5) ?
{FloatOrPercent{100, true},
(m_config.get_abs_value("overhang_1_4_speed", ref_speed) < 0.5) ?
FloatOrPercent{100, true} :
FloatOrPercent{m_config.get_abs_value("overhang_1_4_speed", ref_speed) * 100 / ref_speed, true},
(m_config.get_abs_value("overhang_2_4_speed", ref_speed) < 0.5) ?
@@ -5336,9 +5338,6 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
(m_config.get_abs_value("overhang_4_4_speed", ref_speed) < 0.5) ?
FloatOrPercent{100, true} :
FloatOrPercent{m_config.get_abs_value("overhang_4_4_speed", ref_speed) * 100 / ref_speed, true},
(m_config.get_abs_value("overhang_4_4_speed", ref_speed) < 0.5) ?
FloatOrPercent{100, true} :
FloatOrPercent{m_config.get_abs_value("overhang_4_4_speed", ref_speed) * 100 / ref_speed, true},
(m_config.get_abs_value("overhang_4_4_speed", ref_speed) < 0.5) ?
FloatOrPercent{100, true} :
FloatOrPercent{m_config.get_abs_value("overhang_4_4_speed", ref_speed) * 100 / ref_speed, true}});
@@ -5347,7 +5346,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
ref_speed, speed, m_config.slowdown_for_curled_perimeters);
}else{
ConfigOptionFloatsOrPercents dynamic_overhang_speeds(
{(m_config.get_abs_value("overhang_1_4_speed", ref_speed) < 0.5) ?
{FloatOrPercent{100, true},
(m_config.get_abs_value("overhang_1_4_speed", ref_speed) < 0.5) ?
FloatOrPercent{100, true} :
FloatOrPercent{m_config.get_abs_value("overhang_1_4_speed", ref_speed) * 100 / ref_speed, true},
(m_config.get_abs_value("overhang_2_4_speed", ref_speed) < 0.5) ?
@@ -5356,10 +5356,9 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
(m_config.get_abs_value("overhang_3_4_speed", ref_speed) < 0.5) ?
FloatOrPercent{100, true} :
FloatOrPercent{m_config.get_abs_value("overhang_3_4_speed", ref_speed) * 100 / ref_speed, true},
(m_config.get_abs_value("overhang_4_4_speed", ref_speed) < 0.5) ?
FloatOrPercent{100, true} :
FloatOrPercent{m_config.get_abs_value("overhang_4_4_speed", ref_speed) * 100 / ref_speed, true},
FloatOrPercent{m_config.get_abs_value("bridge_speed") * 100 / ref_speed, true},
(m_config.get_abs_value("overhang_4_4_speed", ref_speed) < 0.5) ?
FloatOrPercent{100, true} :
FloatOrPercent{m_config.get_abs_value("overhang_4_4_speed", ref_speed) * 100 / ref_speed, true},
FloatOrPercent{m_config.get_abs_value("bridge_speed") * 100 / ref_speed, true}});
new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, overhang_overlap_levels, dynamic_overhang_speeds,
@@ -5500,7 +5499,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// { "75%", Overhang_threshold_4_4 },
// { "95%", Overhang_threshold_bridge }
auto check_overhang_fan = [&overhang_fan_threshold](float overlap, ExtrusionRole role) {
if (is_bridge(role)) {
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) {
@@ -5593,7 +5592,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
int overhang_threshold = overhang_fan_threshold == Overhang_threshold_none ? Overhang_threshold_none
: overhang_fan_threshold - 1;
if ((overhang_fan_threshold == Overhang_threshold_none && is_external_perimeter(path.role())) ||
(path.get_overhang_degree() > overhang_threshold || is_bridge(path.role()))) {
(path.get_overhang_degree() > overhang_threshold ||
(path.role() == erBridgeInfill || path.role() == erOverhangPerimeter))) { // ORCA: Add support for separate internal bridge fan speed control
if (!m_is_overhang_fan_on) {
gcode += ";_OVERHANG_FAN_START\n";
m_is_overhang_fan_on = true;
@@ -5604,6 +5604,17 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
gcode += ";_OVERHANG_FAN_END\n";
}
}
if (path.role() == erInternalBridgeInfill) { // ORCA: Add support for separate internal bridge fan speed control
if (!m_is_internal_bridge_fan_on) {
gcode += ";_INTERNAL_BRIDGE_FAN_START\n";
m_is_internal_bridge_fan_on = true;
}
} else {
if (m_is_internal_bridge_fan_on) {
m_is_internal_bridge_fan_on = false;
gcode += ";_INTERNAL_BRIDGE_FAN_END\n";
}
}
}
if (supp_interface_fan_speed >= 0 && path.role() == erSupportMaterialInterface) {
if (!m_is_supp_interface_fan_on) {
@@ -5738,6 +5749,9 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
bool cur_fan_enabled = false;
if( m_enable_cooling_markers && enable_overhang_bridge_fan)
pre_fan_enabled = check_overhang_fan(new_points[0].overlap, path.role());
if(path.role() == erInternalBridgeInfill) // ORCA: Add support for separate internal bridge fan speed control
pre_fan_enabled = true;
double path_length = 0.;
for (size_t i = 1; i < new_points.size(); i++) {
@@ -5761,6 +5775,19 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
pre_fan_enabled = cur_fan_enabled;
}
// ORCA: Add support for separate internal bridge fan speed control
if (path.role() == erInternalBridgeInfill) {
if (!m_is_internal_bridge_fan_on) {
gcode += ";_INTERNAL_BRIDGE_FAN_START\n";
m_is_internal_bridge_fan_on = true;
}
} else {
if (m_is_internal_bridge_fan_on) {
gcode += ";_INTERNAL_BRIDGE_FAN_END\n";
m_is_internal_bridge_fan_on = false;
}
}
if (supp_interface_fan_speed >= 0 && path.role() == erSupportMaterialInterface) {
if (!m_is_supp_interface_fan_on) {
gcode += ";_SUPP_INTERFACE_FAN_START\n";
+1
View File
@@ -515,6 +515,7 @@ private:
std::string _encode_label_ids_to_base64(std::vector<size_t> ids);
// Orca
bool m_is_overhang_fan_on;
bool m_is_internal_bridge_fan_on; // ORCA: Add support for separate internal bridge fan speed control
bool m_is_supp_interface_fan_on;
// Markers for the Pressure Equalizer to recognize the extrusion type.
// The Pressure Equalizer removes the markers from the final G-code.
+39 -4
View File
@@ -65,6 +65,9 @@ struct CoolingLine
TYPE_FORCE_RESUME_FAN = 1 << 14,
TYPE_SUPPORT_INTERFACE_FAN_START = 1 << 15,
TYPE_SUPPORT_INTERFACE_FAN_END = 1 << 16,
// ORCA: Add support for separate internal bridge fan speed control
TYPE_INTERNAL_BRIDGE_FAN_START = 1 << 17,
TYPE_INTERNAL_BRIDGE_FAN_END = 1 << 18,
};
CoolingLine(unsigned int type, size_t line_start, size_t line_end) :
@@ -511,6 +514,10 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
line.type = CoolingLine::TYPE_OVERHANG_FAN_START;
} else if (boost::starts_with(sline, ";_OVERHANG_FAN_END")) {
line.type = CoolingLine::TYPE_OVERHANG_FAN_END;
} else if (boost::starts_with(sline, ";_INTERNAL_BRIDGE_FAN_START")) { // ORCA: Add support for separate internal bridge fan speed control
line.type = CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_START;
} else if (boost::starts_with(sline, ";_INTERNAL_BRIDGE_FAN_END")) { // ORCA: Add support for separate internal bridge fan speed control
line.type = CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_END;
} else if (boost::starts_with(sline, ";_SUPP_INTERFACE_FAN_START")) {
line.type = CoolingLine::TYPE_SUPPORT_INTERFACE_FAN_START;
} else if (boost::starts_with(sline, ";_SUPP_INTERFACE_FAN_END")) {
@@ -705,9 +712,11 @@ std::string CoolingBuffer::apply_layer_cooldown(
new_gcode.reserve(gcode.size() * 2);
bool overhang_fan_control= false;
int overhang_fan_speed = 0;
bool internal_bridge_fan_control= false; // ORCA: Add support for separate internal bridge fan speed control
int internal_bridge_fan_speed = 0; // ORCA: Add support for separate internal bridge fan speed control
bool supp_interface_fan_control= false;
int supp_interface_fan_speed = 0;
auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &overhang_fan_control, &overhang_fan_speed, &supp_interface_fan_control, &supp_interface_fan_speed](bool immediately_apply) {
auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &overhang_fan_control, &overhang_fan_speed, &internal_bridge_fan_control, &internal_bridge_fan_speed, &supp_interface_fan_control, &supp_interface_fan_speed](bool immediately_apply) {
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder)
float fan_min_speed = EXTRUDER_CONFIG(fan_min_speed);
float fan_speed_new = EXTRUDER_CONFIG(reduce_fan_stop_start_freq) ? fan_min_speed : 0;
@@ -749,15 +758,27 @@ std::string CoolingBuffer::apply_layer_cooldown(
supp_interface_fan_speed = EXTRUDER_CONFIG(support_material_interface_fan_speed);
supp_interface_fan_control = supp_interface_fan_speed >= 0;
overhang_fan_control = overhang_fan_speed > fan_speed_new;
// ORCA: Add support for separate internal bridge fan speed control
internal_bridge_fan_speed = EXTRUDER_CONFIG(internal_bridge_fan_speed);
internal_bridge_fan_control = internal_bridge_fan_speed >=0;
if( internal_bridge_fan_speed < 0 ) { // ORCA: Backwards compatibility setting for Orca internal bridge fan speed setting - if set at -1 (which is the default) use the overhang fan speed settings.
internal_bridge_fan_speed = overhang_fan_speed;
internal_bridge_fan_control = overhang_fan_control;
}
#undef EXTRUDER_CONFIG
overhang_fan_control= overhang_fan_speed > fan_speed_new;
} else {
overhang_fan_control= false;
overhang_fan_control = false;
overhang_fan_speed = 0;
fan_speed_new = 0;
additional_fan_speed_new = 0;
supp_interface_fan_control= false;
supp_interface_fan_control = false;
supp_interface_fan_speed = 0;
internal_bridge_fan_control = false; // ORCA: Add support for separate internal bridge fan speed control
internal_bridge_fan_speed = 0; // ORCA: Add support for separate internal bridge fan speed control
}
if (fan_speed_new != m_fan_speed) {
m_fan_speed = fan_speed_new;
@@ -780,6 +801,7 @@ std::string CoolingBuffer::apply_layer_cooldown(
// Orca: Reduce set fan commands by deferring the GCodeWriter::set_fan calls. Inspired by SuperSlicer
// define fan_speed_change_requests and initialize it with all possible types fan speed change requests
std::unordered_map<int, bool> fan_speed_change_requests = {{CoolingLine::TYPE_OVERHANG_FAN_START, false},
{CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_START, false}, // ORCA: Add support for separate internal bridge fan speed control
{CoolingLine::TYPE_SUPPORT_INTERFACE_FAN_START, false},
{CoolingLine::TYPE_FORCE_RESUME_FAN, false}};
bool need_set_fan = false;
@@ -809,6 +831,16 @@ std::string CoolingBuffer::apply_layer_cooldown(
fan_speed_change_requests[CoolingLine::TYPE_OVERHANG_FAN_START] = false;
}
need_set_fan = true;
} else if (line->type & CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_START) { // ORCA: Add support for separate internal bridge fan speed control
if (internal_bridge_fan_control && !fan_speed_change_requests[CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_START]) {
need_set_fan = true;
fan_speed_change_requests[CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_START] = true;
}
} else if (line->type & CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_END) { // ORCA: Add support for separate internal bridge fan speed control
if (internal_bridge_fan_control && fan_speed_change_requests[CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_START]) {
fan_speed_change_requests[CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_START] = false;
}
need_set_fan = true;
} else if (line->type & CoolingLine::TYPE_SUPPORT_INTERFACE_FAN_START) {
if (supp_interface_fan_control && !fan_speed_change_requests[CoolingLine::TYPE_SUPPORT_INTERFACE_FAN_START]) {
fan_speed_change_requests[CoolingLine::TYPE_SUPPORT_INTERFACE_FAN_START] = true;
@@ -917,6 +949,9 @@ std::string CoolingBuffer::apply_layer_cooldown(
if (fan_speed_change_requests[CoolingLine::TYPE_OVERHANG_FAN_START]){
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed);
m_current_fan_speed = overhang_fan_speed;
} else if (fan_speed_change_requests[CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_START]){ // ORCA: Add support for separate internal bridge fan speed control
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, internal_bridge_fan_speed);
m_current_fan_speed = internal_bridge_fan_speed;
}
else if (fan_speed_change_requests[CoolingLine::TYPE_SUPPORT_INTERFACE_FAN_START]){
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, supp_interface_fan_speed);
+3 -1
View File
@@ -65,7 +65,9 @@ public:
: regex_fan_speed("S[0-9]+"),
nb_seconds_delay(nb_seconds_delay>0 ? std::max(0.01f,nb_seconds_delay) : 0),
with_D_option(with_D_option)
, relative_e(relative_e), only_overhangs(only_overhangs), kickstart(kickstart), m_writer(writer){}
, relative_e(relative_e), only_overhangs(only_overhangs), kickstart(kickstart), m_writer(writer){
m_parser.apply_config(writer.config);
}
// Adds the gcode contained in the given string to the analysis and returns it after removing the workcodes
const std::string& process_gcode(const std::string& gcode, bool flush);
+30 -371
View File
@@ -404,359 +404,6 @@ void GCodeProcessor::TimeProcessor::reset()
machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true;
}
void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector<GCodeProcessorResult::MoveVertex>& moves, std::vector<size_t>& lines_ends, size_t total_layer_num)
{
FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") };
if (in.f == nullptr)
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n"));
const bool disable_m73 = this->disable_m73;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": before process %1%")%filename.c_str();
// temporary file to contain modified gcode
std::string out_path = filename + ".postprocess";
FilePtr out{ boost::nowide::fopen(out_path.c_str(), "wb") };
if (out.f == nullptr) {
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n"));
}
auto time_in_minutes = [](float time_in_seconds) {
assert(time_in_seconds >= 0.f);
return int((time_in_seconds + 0.5f) / 60.0f);
};
auto time_in_last_minute = [](float time_in_seconds) {
assert(time_in_seconds <= 60.0f);
return time_in_seconds / 60.0f;
};
auto format_line_M73_main = [](const std::string& mask, int percent, int time) {
char line_M73[64];
sprintf(line_M73, mask.c_str(),
std::to_string(percent).c_str(),
std::to_string(time).c_str());
return std::string(line_M73);
};
auto format_line_M73_stop_int = [](const std::string& mask, int time) {
char line_M73[64];
sprintf(line_M73, mask.c_str(), std::to_string(time).c_str());
return std::string(line_M73);
};
auto format_line_exhaust_fan_control = [](const std::string& mask,int fan_index,int percent) {
char line_fan[64] = { 0 };
sprintf(line_fan,mask.c_str(),
std::to_string(fan_index).c_str(),
std::to_string(int((percent/100.0)*255)).c_str());
return std::string(line_fan);
};
auto format_time_float = [](float time) {
return Slic3r::float_to_string_decimal_point(time, 2);
};
auto format_line_M73_stop_float = [format_time_float](const std::string& mask, float time) {
char line_M73[64];
sprintf(line_M73, mask.c_str(), format_time_float(time).c_str());
return std::string(line_M73);
};
std::string gcode_line;
size_t g1_lines_counter = 0;
// keeps track of last exported pair <percent, remaining time>
std::array<std::pair<int, int>, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_main;
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
last_exported_main[i] = { 0, time_in_minutes(machines[i].time) };
}
// keeps track of last exported remaining time to next printer stop
std::array<int, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_stop;
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
last_exported_stop[i] = time_in_minutes(machines[i].time);
}
// buffer line to export only when greater than 64K to reduce writing calls
std::string export_line;
// replace placeholder lines with the proper final value
// gcode_line is in/out parameter, to reduce expensive memory allocation
auto process_placeholders = [&](std::string& gcode_line) {
int extra_lines_count = 0;
// remove trailing '\n'
auto line = std::string_view(gcode_line).substr(0, gcode_line.length() - 1);
std::string ret;
if (line.length() > 1) {
line = line.substr(1);
if (line == reserved_tag(ETags::First_Line_M73_Placeholder) || line == reserved_tag(ETags::Last_Line_M73_Placeholder)) {
if (disable_m73) {
// Remove current line
gcode_line = "";
return std::tuple(true, -1);
}
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
const TimeMachine& machine = machines[i];
if (machine.enabled) {
// export pair <percent, remaining time>
ret += format_line_M73_main(machine.line_m73_main_mask.c_str(),
(line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? 0 : 100,
(line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? time_in_minutes(machine.time) : 0);
++extra_lines_count;
// export remaining time to next printer stop
if (line == reserved_tag(ETags::First_Line_M73_Placeholder) && !machine.stop_times.empty()) {
int to_export_stop = time_in_minutes(machine.stop_times.front().elapsed_time);
ret += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop);
last_exported_stop[i] = to_export_stop;
++extra_lines_count;
}
}
}
}
else if (line == reserved_tag(ETags::Estimated_Printing_Time_Placeholder)) {
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
const TimeMachine& machine = machines[i];
PrintEstimatedStatistics::ETimeMode mode = static_cast<PrintEstimatedStatistics::ETimeMode>(i);
if (mode == PrintEstimatedStatistics::ETimeMode::Normal || machine.enabled) {
char buf[128];
if(!s_IsBBLPrinter)
// SoftFever: compatibility with klipper_estimator
sprintf(buf, "; estimated printing time (normal mode) = %s\n", get_time_dhms(machine.time).c_str());
else {
//sprintf(buf, "; estimated printing time (%s mode) = %s\n",
// (mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent",
// get_time_dhms(machine.time).c_str());
sprintf(buf, "; model printing time: %s; total estimated time: %s\n",
get_time_dhms(machine.time - machine.prepare_time).c_str(),
get_time_dhms(machine.time).c_str());
}
ret += buf;
}
}
}
//BBS: write total layer number
else if (line == reserved_tag(ETags::Total_Layer_Number_Placeholder)) {
char buf[128];
sprintf(buf, "; total layer number: %zd\n", total_layer_num);
ret += buf;
}
}
if (! ret.empty())
// Not moving the move operator on purpose, so that the gcode_line allocation will grow and it will not be reallocated after handful of lines are processed.
gcode_line = ret;
return std::tuple(!ret.empty(), (extra_lines_count == 0) ? extra_lines_count : extra_lines_count - 1);
};
// check for temporary lines
auto is_temporary_decoration = [](const std::string_view gcode_line) {
// remove trailing '\n'
assert(! gcode_line.empty());
assert(gcode_line.back() == '\n');
// return true for decorations which are used in processing the gcode but that should not be exported into the final gcode
// i.e.:
// bool ret = gcode_line.substr(0, gcode_line.length() - 1) == ";" + Layer_Change_Tag;
// ...
// return ret;
return false;
};
// Iterators for the normal and silent cached time estimate entry recently processed, used by process_line_G1.
auto g1_times_cache_it = Slic3r::reserve_vector<std::vector<TimeMachine::G1LinesCacheItem>::const_iterator>(machines.size());
for (const auto& machine : machines)
g1_times_cache_it.emplace_back(machine.g1_times_cache.begin());
// add lines M73 to exported gcode
auto process_line_move = [
// Lambdas, mostly for string formatting, all with an empty capture block.
time_in_minutes, format_time_float, format_line_M73_main, format_line_M73_stop_int, format_line_M73_stop_float, time_in_last_minute,format_line_exhaust_fan_control,
&self = std::as_const(*this),
// Caches, to be modified
&g1_times_cache_it, &last_exported_main, &last_exported_stop,
// String output
&export_line]
(const size_t g1_lines_counter) {
unsigned int exported_lines_count = 0;
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
const TimeMachine& machine = self.machines[i];
if (machine.enabled) {
// export pair <percent, remaining time>
// Skip all machine.g1_times_cache below g1_lines_counter.
auto& it = g1_times_cache_it[i];
while (it != machine.g1_times_cache.end() && it->id < g1_lines_counter)
++it;
if (it != machine.g1_times_cache.end() && it->id == g1_lines_counter) {
std::pair<int, int> to_export_main = { int(100.0f * it->elapsed_time / machine.time),
time_in_minutes(machine.time - it->elapsed_time) };
if (last_exported_main[i] != to_export_main) {
export_line += format_line_M73_main(machine.line_m73_main_mask.c_str(),
to_export_main.first, to_export_main.second);
last_exported_main[i] = to_export_main;
++exported_lines_count;
}
// export remaining time to next printer stop
auto it_stop = std::upper_bound(machine.stop_times.begin(), machine.stop_times.end(), it->elapsed_time,
[](float value, const TimeMachine::StopTime& t) { return value < t.elapsed_time; });
if (it_stop != machine.stop_times.end()) {
int to_export_stop = time_in_minutes(it_stop->elapsed_time - it->elapsed_time);
if (last_exported_stop[i] != to_export_stop) {
if (to_export_stop > 0) {
if (last_exported_stop[i] != to_export_stop) {
export_line += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop);
last_exported_stop[i] = to_export_stop;
++exported_lines_count;
}
}
else {
bool is_last = false;
auto next_it = it + 1;
is_last |= (next_it == machine.g1_times_cache.end());
if (next_it != machine.g1_times_cache.end()) {
auto next_it_stop = std::upper_bound(machine.stop_times.begin(), machine.stop_times.end(), next_it->elapsed_time,
[](float value, const TimeMachine::StopTime& t) { return value < t.elapsed_time; });
is_last |= (next_it_stop != it_stop);
std::string time_float_str = format_time_float(time_in_last_minute(it_stop->elapsed_time - it->elapsed_time));
std::string next_time_float_str = format_time_float(time_in_last_minute(it_stop->elapsed_time - next_it->elapsed_time));
is_last |= (string_to_double_decimal_point(time_float_str) > 0. && string_to_double_decimal_point(next_time_float_str) == 0.);
}
if (is_last) {
if (std::distance(machine.stop_times.begin(), it_stop) == static_cast<ptrdiff_t>(machine.stop_times.size() - 1))
export_line += format_line_M73_stop_int(machine.line_m73_stop_mask.c_str(), to_export_stop);
else
export_line += format_line_M73_stop_float(machine.line_m73_stop_mask.c_str(), time_in_last_minute(it_stop->elapsed_time - it->elapsed_time));
last_exported_stop[i] = to_export_stop;
++exported_lines_count;
}
}
}
}
}
}
}
return exported_lines_count;
};
// helper function to write to disk
size_t out_file_pos = 0;
lines_ends.clear();
auto write_string = [&export_line, &out, &out_path, &out_file_pos, &lines_ends](const std::string& str) {
fwrite((const void*)export_line.c_str(), 1, export_line.length(), out.f);
if (ferror(out.f)) {
out.close();
boost::nowide::remove(out_path.c_str());
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n"));
}
for (size_t i = 0; i < export_line.size(); ++ i)
if (export_line[i] == '\n')
lines_ends.emplace_back(out_file_pos + i + 1);
out_file_pos += export_line.size();
export_line.clear();
};
unsigned int line_id = 0;
std::vector<std::pair<unsigned int, unsigned int>> offsets;
{
// Read the input stream 64kB at a time, extract lines and process them.
std::vector<char> buffer(65536 * 10, 0);
// Line buffer.
assert(gcode_line.empty());
for (;;) {
size_t cnt_read = ::fread(buffer.data(), 1, buffer.size(), in.f);
if (::ferror(in.f))
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n"));
bool eof = cnt_read == 0;
auto it = buffer.begin();
auto it_bufend = buffer.begin() + cnt_read;
while (it != it_bufend || (eof && ! gcode_line.empty())) {
// Find end of line.
bool eol = false;
auto it_end = it;
for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) ;
// End of line is indicated also if end of file was reached.
eol |= eof && it_end == it_bufend;
gcode_line.insert(gcode_line.end(), it, it_end);
if (eol) {
++line_id;
// determine the end of line character and pass to output
gcode_line += *it_end;
if(*it_end == '\r' && *(++ it_end) == '\n')
gcode_line += '\n';
// replace placeholder lines
auto [processed, lines_added_count] = process_placeholders(gcode_line);
if (processed && lines_added_count != 0)
offsets.push_back({ line_id, lines_added_count });
if (!disable_m73 && !processed &&!is_temporary_decoration(gcode_line) &&
(GCodeReader::GCodeLine::cmd_is(gcode_line, "G1") ||
GCodeReader::GCodeLine::cmd_is(gcode_line, "G2") ||
GCodeReader::GCodeLine::cmd_is(gcode_line, "G3") ||
GCodeReader::GCodeLine::cmd_is(gcode_line, "G10")||
GCodeReader::GCodeLine::cmd_is(gcode_line, "G11"))) {
// remove temporary lines, add lines M73 where needed
unsigned int extra_lines_count = process_line_move(g1_lines_counter ++);
if (extra_lines_count > 0)
offsets.push_back({ line_id, extra_lines_count });
}
if (disable_m73 && !processed && GCodeReader::GCodeLine::cmd_is(gcode_line, "M73")) {
// Remove any existing M73 command
gcode_line = "";
offsets.push_back({line_id, -1});
}
export_line += gcode_line;
if (export_line.length() > 65535)
write_string(export_line);
gcode_line.clear();
}
// Skip EOL.
it = it_end;
if (it != it_bufend && *it == '\r')
++ it;
if (it != it_bufend && *it == '\n')
++ it;
}
if (eof)
break;
}
}
if (!export_line.empty())
write_string(export_line);
out.close();
in.close();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": after process %1%")%filename.c_str();
// updates moves' gcode ids which have been modified by the insertion of the M73 lines
unsigned int curr_offset_id = 0;
unsigned int total_offset = 0;
for (GCodeProcessorResult::MoveVertex& move : moves) {
while (curr_offset_id < static_cast<unsigned int>(offsets.size()) && offsets[curr_offset_id].first <= move.gcode_id) {
total_offset += offsets[curr_offset_id].second;
++curr_offset_id;
}
move.gcode_id += total_offset;
}
if (rename_file(out_path, filename)) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": Failed to rename the output G-code file from %1% to %2%")%out_path.c_str() % filename.c_str();
throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' +
"Is " + out_path + " locked?" + '\n');
}
}
void GCodeProcessor::UsedFilaments::reset()
{
color_change_cache = 0.0f;
@@ -1136,7 +783,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
DEFAULT_TRAVEL_ACCELERATION;
}
m_time_processor.disable_m73 = config.disable_m73;
m_disable_m73 = config.disable_m73;
const ConfigOptionFloat* initial_layer_print_height = config.option<ConfigOptionFloat>("initial_layer_print_height");
if (initial_layer_print_height != nullptr)
@@ -1694,9 +1341,6 @@ void GCodeProcessor::finalize(bool post_process)
m_height_compare.output();
m_width_compare.output();
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
if (post_process){
m_time_processor.post_process(m_result.filename, m_result.moves, m_result.lines_ends, m_layer_id);
}
#if ENABLE_GCODE_VIEWER_STATISTICS
m_result.time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_start_time).count();
#endif // ENABLE_GCODE_VIEWER_STATISTICS
@@ -4411,7 +4055,10 @@ void GCodeProcessor::run_post_process()
return time_in_seconds / 60.0f;
};
auto format_line_M73_main = [](const std::string& mask, int percent, int time) {
auto format_line_M73_main = [this](const std::string& mask, int percent, int time) {
if(this->m_disable_m73)
return std::string("");
char line_M73[64];
sprintf(line_M73, mask.c_str(),
std::to_string(percent).c_str(),
@@ -4419,7 +4066,9 @@ void GCodeProcessor::run_post_process()
return std::string(line_M73);
};
auto format_line_M73_stop_int = [](const std::string& mask, int time) {
auto format_line_M73_stop_int = [this](const std::string& mask, int time) {
if (this->m_disable_m73)
return std::string("");
char line_M73[64];
sprintf(line_M73, mask.c_str(), std::to_string(time).c_str());
return std::string(line_M73);
@@ -4766,33 +4415,43 @@ void GCodeProcessor::run_post_process()
}
}
}
}
else if (line == reserved_tag(ETags::Estimated_Printing_Time_Placeholder)) {
} else if (line == reserved_tag(ETags::Estimated_Printing_Time_Placeholder)) {
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
const TimeMachine& machine = m_time_processor.machines[i];
PrintEstimatedStatistics::ETimeMode mode = static_cast<PrintEstimatedStatistics::ETimeMode>(i);
const TimeMachine& machine = m_time_processor.machines[i];
PrintEstimatedStatistics::ETimeMode mode = static_cast<PrintEstimatedStatistics::ETimeMode>(i);
if (mode == PrintEstimatedStatistics::ETimeMode::Normal || machine.enabled) {
char buf[128];
sprintf(buf, "; estimated printing time (%s mode) = %s\n",
(mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent",
get_time_dhms(machine.time).c_str());
if (!s_IsBBLPrinter)
// Orca: compatibility with klipper_estimator
sprintf(buf, "; estimated printing time (%s mode) = %s\n",
(mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent",
get_time_dhms(machine.time).c_str());
else {
sprintf(buf, "; model printing time: %s; total estimated time: %s\n",
get_time_dhms(machine.time - machine.prepare_time).c_str(), get_time_dhms(machine.time).c_str());
}
export_lines.append_line(buf);
processed = true;
}
}
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
const TimeMachine& machine = m_time_processor.machines[i];
PrintEstimatedStatistics::ETimeMode mode = static_cast<PrintEstimatedStatistics::ETimeMode>(i);
const TimeMachine& machine = m_time_processor.machines[i];
PrintEstimatedStatistics::ETimeMode mode = static_cast<PrintEstimatedStatistics::ETimeMode>(i);
if (mode == PrintEstimatedStatistics::ETimeMode::Normal || machine.enabled) {
char buf[128];
sprintf(buf, "; estimated first layer printing time (%s mode) = %s\n",
(mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent",
get_time_dhms(machine.prepare_time).c_str());
(mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent",
get_time_dhms(machine.prepare_time).c_str());
export_lines.append_line(buf);
processed = true;
}
}
}
// Orca: write total layer number, this is used by Bambu printers only as of now
else if (line == reserved_tag(ETags::Total_Layer_Number_Placeholder)) {
char buf[128];
sprintf(buf, "; total layer number: %u\n", m_layer_id);
export_lines.append_line(buf);
}
}
return processed;
+3 -7
View File
@@ -380,7 +380,7 @@ class Print;
EMoveType move_type{ EMoveType::Noop };
ExtrusionRole role{ erNone };
unsigned int g1_line_id{ 0 };
unsigned int remaining_internal_g1_lines;
unsigned int remaining_internal_g1_lines{ 0 };
unsigned int layer_id{ 0 };
float distance{ 0.0f }; // mm
float acceleration{ 0.0f }; // mm/s^2
@@ -429,7 +429,7 @@ class Print;
struct G1LinesCacheItem
{
unsigned int id;
unsigned int remaining_internal_g1_lines;
unsigned int remaining_internal_g1_lines{ 0 };
float elapsed_time;
};
@@ -495,15 +495,10 @@ class Print;
float filament_unload_times;
//Orca: time for tool change
float machine_tool_change_time;
bool disable_m73;
std::array<TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> machines;
void reset();
// post process the file with the given filename to add remaining time lines M73
// and updates moves' gcode ids accordingly
void post_process(const std::string& filename, std::vector<GCodeProcessorResult::MoveVertex>& moves, std::vector<size_t>& lines_ends, size_t total_layer_num);
};
struct UsedFilaments // filaments per ColorChange
@@ -735,6 +730,7 @@ class Print;
bool m_single_extruder_multi_material;
float m_preheat_time;
int m_preheat_steps;
bool m_disable_m73;
#if ENABLE_GCODE_VIEWER_STATISTICS
std::chrono::time_point<std::chrono::high_resolution_clock> m_start_time;
#endif // ENABLE_GCODE_VIEWER_STATISTICS
+13 -6
View File
@@ -121,9 +121,13 @@ std::string SpiralVase::process_layer(const std::string &gcode, bool last_layer)
// layer.
bool transition_in = m_transition_layer && m_config.use_relative_e_distances.value;
bool transition_out = last_layer && m_config.use_relative_e_distances.value;
float starting_flowrate = float(m_config.spiral_starting_flow_ratio.value);
float finishing_flowrate = float(m_config.spiral_finishing_flow_ratio.value);
float len = 0.f;
SpiralVase::SpiralPoint last_point = previous_layer != NULL && previous_layer->size() >0? previous_layer->at(previous_layer->size()-1): SpiralVase::SpiralPoint(0,0);
m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height, transition_in, &len, &current_layer, &previous_layer, &transition_gcode, transition_out, smooth_spiral, &max_xy_dist_for_smoothing, &last_point]
m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height, transition_in, &len, &current_layer, &previous_layer, &transition_gcode, transition_out, smooth_spiral, &max_xy_dist_for_smoothing, &last_point, starting_flowrate, finishing_flowrate]
(GCodeReader &reader, GCodeReader::GCodeLine line) {
if (line.cmd_is("G1")) {
// Orca: Filter out retractions at layer change
@@ -140,15 +144,18 @@ std::string SpiralVase::process_layer(const std::string &gcode, bool last_layer)
if (dist_XY > 0 && line.extruding(reader)) { // Exclude wipe and retract
len += dist_XY;
float factor = len / total_layer_length;
if (transition_in)
// Transition layer, interpolate the amount of extrusion from zero to the final value.
line.set(E, line.e() * factor, 5 /*decimal_digits*/);
else if (transition_out) {
if (transition_in){
// Transition layer, interpolate the amount of extrusion starting from spiral_vase_starting_flow_rate to 100%.
float starting_e_factor = starting_flowrate + (factor * (1.f - starting_flowrate));
line.set(E, line.e() * starting_e_factor, 5 /*decimal_digits*/);
} else if (transition_out) {
// We want the last layer to ramp down extrusion, but without changing z height!
// So clone the line before we mess with its Z and duplicate it into a new layer that ramps down E
// We add this new layer at the very end
// As with transition_in, the amount is ramped down from 100% to spiral_vase_finishing_flow_rate
GCodeReader::GCodeLine transitionLine(line);
transitionLine.set(E, line.e() * (1 - factor), 5 /*decimal_digits*/);
float finishing_e_factor = finishing_flowrate + ((1.f -factor) * (1.f - finishing_flowrate));
transitionLine.set(E, line.e() * finishing_e_factor, 5 /*decimal_digits*/);
transition_gcode += transitionLine.raw() + '\n';
}
// This line is the core of Spiral Vase mode, ramp up the Z smoothly
+1 -1
View File
@@ -842,7 +842,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume()
const unsigned int number_of_extruders = (unsigned int) (sqrt(flush_matrix.size()) + EPSILON);
// Extract purging volumes for each extruder pair:
std::vector<std::vector<float>> wipe_volumes;
if (print_config->purge_in_prime_tower || m_is_BBL_printer) {
if ((print_config->purge_in_prime_tower && print_config->single_extruder_multi_material) || m_is_BBL_printer) {
for (unsigned int i = 0; i < number_of_extruders; ++i)
wipe_volumes.push_back( std::vector<float>(flush_matrix.begin() + i * number_of_extruders,
flush_matrix.begin() + (i + 1) * number_of_extruders));
+43 -23
View File
@@ -14,6 +14,7 @@
namespace Slic3r
{
static const double wipe_tower_wall_infill_overlap = 0.0;
inline float align_round(float value, float base)
{
@@ -118,7 +119,7 @@ public:
WipeTowerWriter& set_initial_tool(size_t tool) { m_current_tool = tool; return *this; }
WipeTowerWriter& set_z(float z)
WipeTowerWriter& set_z(float z)
{ m_current_z = z; return *this; }
WipeTowerWriter& set_extrusion_flow(float flow)
@@ -237,7 +238,7 @@ public:
WipeTowerWriter& travel(float x, float y, float f = 0.f)
{ return extrude_explicit(x, y, 0.f, f); }
WipeTowerWriter& travel(const Vec2f &dest, float f = 0.f)
WipeTowerWriter& travel(const Vec2f &dest, float f = 0.f)
{ return extrude_explicit(dest.x(), dest.y(), 0.f, f); }
// Extrude a line from current position to x, y with the extrusion amount given by m_extrusion_flow.
@@ -248,7 +249,7 @@ public:
return extrude_explicit(x, y, std::sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true);
}
WipeTowerWriter& extrude(const Vec2f &dest, const float f = 0.f)
WipeTowerWriter& extrude(const Vec2f &dest, const float f = 0.f)
{ return extrude(dest.x(), dest.y(), f); }
WipeTowerWriter& rectangle(const Vec2f& ld,float width,float height,const float f = 0.f)
@@ -299,7 +300,7 @@ public:
do {
++i;
if (i == 4) i = 0;
if (need_change_flow) {
if (need_change_flow) {
if (i == 1) {
// using bridge flow in bridge area, and add notes for gcode-check when flow changed
set_extrusion_flow(wipe_tower->extrusion_flow(0.2));
@@ -358,7 +359,7 @@ public:
// Elevate the extruder head above the current print_z position.
WipeTowerWriter& z_hop(float hop, float f = 0.f)
{
{
m_gcode += std::string("G1") + set_format_Z(m_current_z + hop);
if (f != 0 && f != m_current_feedrate)
m_gcode += set_format_F(f);
@@ -367,7 +368,7 @@ public:
}
// Lower the extruder head back to the current print_z position.
WipeTowerWriter& z_hop_reset(float f = 0.f)
WipeTowerWriter& z_hop_reset(float f = 0.f)
{ return z_hop(0, f); }
// Move to x1, +y_increment,
@@ -455,14 +456,14 @@ public:
}
WipeTowerWriter& flush_planner_queue()
{
m_gcode += "G4 S0\n";
{
m_gcode += "G4 S0\n";
return *this;
}
// Reset internal extruder counter.
WipeTowerWriter& reset_extruder()
{
{
m_gcode += "G92 E0\n";
return *this;
}
@@ -725,7 +726,7 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
// Returns gcode to prime the nozzles at the front edge of the print bed.
std::vector<WipeTower::ToolChangeResult> WipeTower::prime(
// print_z of the first layer.
float initial_layer_print_height,
float initial_layer_print_height,
// Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
const std::vector<unsigned int> &tools,
// If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
@@ -742,7 +743,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_per
float wipe_depth = 0.f;
float wipe_length = 0.f;
float purge_volume = 0.f;
// Finds this toolchange info
if (tool != (unsigned int)(-1))
{
@@ -802,12 +803,30 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_per
box_coordinates wt_box(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED) ? m_layer_info->toolchanges_depth() - m_layer_info->depth : 0.f),
m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
// align the perimeter
Vec2f pos = initial_position;
switch (m_cur_layer_id % 4){
case 0:
pos = wt_box.ld;
break;
case 1:
pos = wt_box.rd;
break;
case 2:
pos = wt_box.ru;
break;
case 3:
pos = wt_box.lu;
break;
default: break;
}
writer.set_initial_position(pos, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
wt_box = align_perimeter(wt_box);
writer.rectangle(wt_box);
writer.travel(initial_position);
}
if (first_toolchange_to_nonsoluble) {
{
writer.travel(Vec2f(0, 0));
writer.travel(initial_position);
}
@@ -849,7 +868,7 @@ void WipeTower::toolchange_Unload(
#if 0
float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width;
float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width;
const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness
const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm
@@ -1108,9 +1127,9 @@ void WipeTower::toolchange_Wipe(
}
if (m_left_to_right)
writer.extrude(xr + 0.25f * m_perimeter_width, writer.y(), wipe_speed);
writer.extrude(xr + wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), wipe_speed);
else
writer.extrude(xl - 0.25f * m_perimeter_width, writer.y(), wipe_speed);
writer.extrude(xl - wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), wipe_speed);
// BBS: recover the flow in non-bridging area
if (need_change_flow) {
@@ -1139,7 +1158,7 @@ void WipeTower::toolchange_Wipe(
//writer.add_wipe_point(writer.x(), writer.y())
// .add_wipe_point(writer.x(), writer.y() - dy)
// .add_wipe_point(! m_left_to_right ? m_wipe_tower_width : 0.f, writer.y() - dy);
// BBS: modify the wipe_path after toolchange
// BBS: modify the wipe_path after toolchange
writer.add_wipe_point(writer.x(), writer.y())
.add_wipe_point(! m_left_to_right ? m_wipe_tower_width : 0.f, writer.y());
@@ -1473,7 +1492,7 @@ void WipeTower::plan_tower()
m_wipe_tower_depth = 0.f;
for (auto& layer : m_plan)
layer.depth = 0.f;
float max_depth_for_all = 0;
for (int layer_index = int(m_plan.size()) - 1; layer_index >= 0; --layer_index)
{
@@ -1482,7 +1501,7 @@ void WipeTower::plan_tower()
this_layer_depth = min_wipe_tower_depth;
m_plan[layer_index].depth = this_layer_depth;
if (this_layer_depth > m_wipe_tower_depth - m_perimeter_width)
m_wipe_tower_depth = this_layer_depth + m_perimeter_width;
@@ -1492,7 +1511,7 @@ void WipeTower::plan_tower()
m_plan[i].depth = this_layer_depth;
}
if (m_enable_timelapse_print && layer_index == 0)
if (m_enable_timelapse_print && layer_index == 0)
max_depth_for_all = m_plan[0].depth;
}
@@ -1600,10 +1619,11 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
used = 0.f;
m_old_temperature = -1; // reset last temperature written in the gcode
int index = 0;
std::vector<WipeTower::ToolChangeResult> layer_result;
for (auto layer : m_plan)
{
m_cur_layer_id = index++;
set_layer(layer.z, layer.height, 0, false/*layer.z == m_plan.front().z*/, layer.z == m_plan.back().z);
// BBS
//m_internal_rotation += 180.f;
@@ -1627,14 +1647,14 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
// if there is no toolchange switching to non-soluble, finish layer
// will be called at the very beginning. That's the last possibility
// where a nonsoluble tool can be.
if (m_enable_timelapse_print) {
if (m_enable_timelapse_print) {
timelapse_wall = only_generate_out_wall();
}
finish_layer_tcr = finish_layer(m_enable_timelapse_print ? false : true, layer.extruder_fill);
}
for (int i=0; i<int(layer.tool_changes.size()); ++i) {
if (i == 0 && m_enable_timelapse_print) {
if (i == 0 && m_enable_timelapse_print) {
timelapse_wall = only_generate_out_wall();
}
+1 -1
View File
@@ -304,7 +304,7 @@ private:
float m_travel_speed = 0.f;
float m_first_layer_speed = 0.f;
size_t m_first_layer_idx = size_t(-1);
size_t m_cur_layer_id;
// G-code generator parameters.
float m_cooling_tube_retraction = 0.f;
float m_cooling_tube_length = 0.f;
+14 -10
View File
@@ -1601,20 +1601,24 @@ void WipeTower2::save_on_last_wipe()
auto& toolchange = m_layer_info->tool_changes[i];
tool_change(toolchange.new_tool);
if (i == idx) {
float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
// Orca: allow calculation of the required depth and wipe volume for soluable toolchanges as well
// NOTE: it's not clear if this is the right way, technically we should disable wipe tower if soluble filament is used as it
// will will make the wipe tower unstable. Need to revist this in the future.
float volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, m_layer_info->height);
float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save);
float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, m_perimeter_width*m_extra_flow, m_layer_info->height));
float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width);
// if (i == idx) {
float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe;
toolchange.wipe_volume = volume_left_to_wipe;
}
}
float volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, m_layer_info->height);
float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save);
float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, m_perimeter_width*m_extra_flow, m_layer_info->height));
float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width);
toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe;
toolchange.wipe_volume = volume_left_to_wipe;
// }
}
}
}
// Return index of first toolchange that switches to non-soluble extruder
-6
View File
@@ -447,12 +447,6 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
// this function, fix it first.
//std::terminate();
// Orca: If moving down during below the current layer nominal Z, force XY->Z moves to avoid collisions with previous extrusions
double nominal_z = m_pos(2) - m_lifted;
if (point(2) < nominal_z - EPSILON) { // EPSILON to avoid false matches due to rounding errors
this->set_current_position_clear(false); // This forces XYZ moves to be split into XY->Z
}
/* If target Z is lower than current Z but higher than nominal Z we
don't perform the Z move but we only move in the XY plane and
adjust the nominal Z by reducing the lift amount that will be
+1
View File
@@ -391,6 +391,7 @@ coordf_t Layer::get_sparse_infill_max_void_area()
max_void_area = std::max(max_void_area, spacing * spacing);
break;
case ipGrid:
case ip2DLattice:
case ipHoneycomb:
case ipLightning:
max_void_area = std::max(max_void_area, 4.0 * spacing * spacing);
+15
View File
@@ -1063,6 +1063,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs)
this->sla_support_points = rhs.sla_support_points;
this->sla_points_status = rhs.sla_points_status;
this->sla_drain_holes = rhs.sla_drain_holes;
this->brim_points = rhs.brim_points;
this->layer_config_ranges = rhs.layer_config_ranges;
this->layer_height_profile = rhs.layer_height_profile;
this->printable = rhs.printable;
@@ -1102,6 +1103,7 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs)
this->sla_support_points = std::move(rhs.sla_support_points);
this->sla_points_status = std::move(rhs.sla_points_status);
this->sla_drain_holes = std::move(rhs.sla_drain_holes);
this->brim_points = std::move(brim_points);
this->layer_config_ranges = std::move(rhs.layer_config_ranges);
this->layer_height_profile = std::move(rhs.layer_height_profile);
this->printable = std::move(rhs.printable);
@@ -1736,6 +1738,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con
new_object->sla_support_points.clear();
new_object->sla_drain_holes.clear();
new_object->sla_points_status = sla::PointsStatus::NoPoints;
new_object->brim_points.clear();
new_object->clear_volumes();
new_object->input_file.clear();
@@ -2040,6 +2043,7 @@ ModelObjectPtrs ModelObject::merge_volumes(std::vector<int>& vol_indeces)
upper->sla_support_points.clear();
upper->sla_drain_holes.clear();
upper->sla_points_status = sla::PointsStatus::NoPoints;
upper->brim_points.clear();
upper->clear_volumes();
upper->input_file.clear();
@@ -3510,6 +3514,17 @@ bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObjec
[](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mmu_segmentation_facets.timestamp_matches(mv_new.mmu_segmentation_facets); });
}
bool model_brim_points_data_changed(const ModelObject& mo, const ModelObject& mo_new)
{
if (mo.brim_points.size() != mo_new.brim_points.size())
return true;
for (size_t i = 0; i < mo.brim_points.size(); ++i) {
if (mo.brim_points[i] != mo_new.brim_points[i])
return true;
}
return false;
}
bool model_has_multi_part_objects(const Model &model)
{
for (const ModelObject *model_object : model.objects)
+10 -5
View File
@@ -10,6 +10,7 @@
#include "Slicing.hpp"
#include "SLA/SupportPoint.hpp"
#include "SLA/Hollowing.hpp"
#include "BrimEarsPoint.hpp"
#include "TriangleMesh.hpp"
#include "CustomGCode.hpp"
#include "calib.hpp"
@@ -383,9 +384,7 @@ public:
// Holes to be drilled into the object so resin can flow out
sla::DrainHoles sla_drain_holes;
// Connectors to be added into the object before cut and are used to create a solid/negative volumes during a cut perform
CutConnectors cut_connectors;
CutObjectBase cut_id;
BrimPoints brim_points;
/* This vector accumulates the total translation applied to the object by the
center_around_origin() method. Callers might want to apply the same translation
@@ -396,6 +395,10 @@ public:
// BBS: save for compare with new load volumes
std::vector<ObjectID> volume_ids;
// Connectors to be added into the object before cut and are used to create a solid/negative volumes during a cut perform
CutConnectors cut_connectors;
CutObjectBase cut_id;
Model* get_model() { return m_model; }
const Model* get_model() const { return m_model; }
// BBS: production extension
@@ -673,7 +676,7 @@ private:
Internal::StaticSerializationWrapper<ModelConfigObject const> config_wrapper(config);
Internal::StaticSerializationWrapper<LayerHeightProfile const> layer_heigth_profile_wrapper(layer_height_profile);
ar(name, module_name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper,
sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation,
sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, brim_points,
m_bounding_box_approx, m_bounding_box_approx_valid,
m_bounding_box_exact, m_bounding_box_exact_valid, m_min_max_z_valid,
m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid,
@@ -686,7 +689,7 @@ private:
// BBS: add backup, check modify
SaveObjectGaurd gaurd(*this);
ar(name, module_name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper,
sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation,
sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, brim_points,
m_bounding_box_approx, m_bounding_box_approx_valid,
m_bounding_box_exact, m_bounding_box_exact_valid, m_min_max_z_valid,
m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid,
@@ -1694,6 +1697,8 @@ bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo
// The function assumes that volumes list is synchronized.
extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new);
bool model_brim_points_data_changed(const ModelObject& mo, const ModelObject& mo_new);
// If the model has multi-part objects, then it is currently not supported by the SLA mode.
// Either the model cannot be loaded, or a SLA printer has to be activated.
bool model_has_multi_part_objects(const Model &model);
+9 -7
View File
@@ -767,6 +767,8 @@ BedType Preset::get_default_bed_type(PresetBundle* preset_bundle)
return BedType::btPC;
} else if (model_id == "C11") {
return BedType::btPEI;
}else if (model_id == "Elegoo-CC" || model_id == "Elegoo-C") {//set default bed type to PTE for Elegoo-CC
return BedType::btPTE;
}
return BedType::btPEI;
}
@@ -781,10 +783,10 @@ bool Preset::has_cali_lines(PresetBundle* preset_bundle)
}
static std::vector<std::string> s_Preset_print_options {
"layer_height", "initial_layer_print_height", "wall_loops", "alternate_extra_wall", "slice_closing_radius", "spiral_mode", "spiral_mode_smooth", "spiral_mode_max_xy_smoothing", "slicing_mode",
"layer_height", "initial_layer_print_height", "wall_loops", "alternate_extra_wall", "slice_closing_radius", "spiral_mode", "spiral_mode_smooth", "spiral_mode_max_xy_smoothing", "spiral_starting_flow_ratio", "spiral_finishing_flow_ratio", "slicing_mode",
"top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness",
"extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "overhang_reverse", "overhang_reverse_threshold","overhang_reverse_internal_only", "wall_direction",
"seam_position", "staggered_inner_seams", "wall_sequence", "is_infill_first", "sparse_infill_density", "sparse_infill_pattern", "top_surface_pattern", "bottom_surface_pattern",
"seam_position", "staggered_inner_seams", "wall_sequence", "is_infill_first", "sparse_infill_density", "sparse_infill_pattern", "lattice_angle_1", "lattice_angle_2", "top_surface_pattern", "bottom_surface_pattern",
"infill_direction", "solid_infill_direction", "rotate_solid_infill_direction", "counterbore_hole_bridging",
"minimum_sparse_infill_area", "reduce_infill_retraction","internal_solid_infill_pattern","gap_fill_target",
"ironing_type", "ironing_pattern", "ironing_flow", "ironing_speed", "ironing_spacing", "ironing_angle", "ironing_inset",
@@ -801,7 +803,7 @@ static std::vector<std::string> s_Preset_print_options {
"independent_support_layer_height",
"support_angle", "support_interface_top_layers", "support_interface_bottom_layers",
"support_interface_pattern", "support_interface_spacing", "support_interface_loop_pattern",
"support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "bridge_no_support", "thick_bridges", "thick_internal_bridges","dont_filter_internal_bridges", "max_bridge_length", "print_sequence", "print_order", "support_remove_small_overhang",
"support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "bridge_no_support", "thick_bridges", "thick_internal_bridges","dont_filter_internal_bridges","enable_extra_bridge_layer", "max_bridge_length", "print_sequence", "print_order", "support_remove_small_overhang",
"filename_format", "wall_filament", "support_bottom_z_distance",
"sparse_infill_filament", "solid_infill_filament", "support_filament", "support_interface_filament","support_interface_not_for_body",
"ooze_prevention", "standby_temperature_delta", "preheat_time","preheat_steps", "interface_shells", "line_width", "initial_layer_line_width",
@@ -820,11 +822,11 @@ static std::vector<std::string> s_Preset_print_options {
"timelapse_type",
"wall_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",
"wall_distribution_count", "min_feature_size", "min_bead_width", "post_process", "min_length_factor",
"small_perimeter_speed", "small_perimeter_threshold","bridge_angle", "filter_out_gap_fill", "travel_acceleration","inner_wall_acceleration", "min_width_top_surface",
"small_perimeter_speed", "small_perimeter_threshold","bridge_angle","internal_bridge_angle", "filter_out_gap_fill", "travel_acceleration","inner_wall_acceleration", "min_width_top_surface",
"default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk","travel_jerk",
"top_solid_infill_flow_ratio","bottom_solid_infill_flow_ratio","only_one_wall_first_layer", "print_flow_ratio", "seam_gap",
"role_based_wipe_speed", "wipe_speed", "accel_to_decel_enable", "accel_to_decel_factor", "wipe_on_loops", "wipe_before_external_loop",
"bridge_density", "precise_outer_wall", "overhang_speed_classic", "bridge_acceleration",
"bridge_density","internal_bridge_density", "precise_outer_wall", "overhang_speed_classic", "bridge_acceleration",
"sparse_infill_acceleration", "internal_solid_infill_acceleration", "tree_support_adaptive_layer_height", "tree_support_auto_brim",
"tree_support_brim_width", "gcode_comments", "gcode_label_objects",
"initial_layer_travel_speed", "exclude_object", "slow_down_layers", "infill_anchor", "infill_anchor_max","initial_layer_min_bead_width",
@@ -860,7 +862,7 @@ static std::vector<std::string> s_Preset_filament_options {
"filament_wipe_distance", "additional_cooling_fan_speed",
"nozzle_temperature_range_low", "nozzle_temperature_range_high",
//SoftFever
"enable_pressure_advance", "pressure_advance","adaptive_pressure_advance","adaptive_pressure_advance_model","adaptive_pressure_advance_overhangs", "adaptive_pressure_advance_bridges","chamber_temperature", "filament_shrink","filament_shrinkage_compensation_z", "support_material_interface_fan_speed", "filament_notes" /*,"filament_seam_gap"*/,
"enable_pressure_advance", "pressure_advance","adaptive_pressure_advance","adaptive_pressure_advance_model","adaptive_pressure_advance_overhangs", "adaptive_pressure_advance_bridges","chamber_temperature", "filament_shrink","filament_shrinkage_compensation_z", "support_material_interface_fan_speed","internal_bridge_fan_speed", "filament_notes" /*,"filament_seam_gap"*/,
"filament_loading_speed", "filament_loading_speed_start",
"filament_unloading_speed", "filament_unloading_speed_start", "filament_toolchange_delay", "filament_cooling_moves", "filament_stamping_loading_speed", "filament_stamping_distance",
"filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters",
@@ -2459,7 +2461,7 @@ const Preset *PresetCollection::get_preset_base(const Preset &child) const
// Handle user preset
if (child.inherits().empty())
return &child; // this is user root
auto inherits = find_preset(child.inherits());
auto inherits = find_preset2(child.inherits(),true);
return inherits ? get_preset_base(*inherits) : nullptr;
}
+4 -1
View File
@@ -610,7 +610,10 @@ public:
// Orca: find preset, if not found, keep searching in the renamed history. This is function should only be used when find
// system(parent) presets for custom preset.
Preset* find_preset2(const std::string& name, bool auto_match = true);
const Preset* find_preset2(const std::string& name, bool auto_match = true) const
{
return const_cast<PresetCollection*>(this)->find_preset2(name, auto_match);
}
size_t first_visible_idx() const;
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
// If one of the prefered_alternates is compatible, select it.
+4 -3
View File
@@ -193,6 +193,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"gcode_label_objects",
"exclude_object",
"support_material_interface_fan_speed",
"internal_bridge_fan_speed", // ORCA: Add support for separate internal bridge fan speed control
"single_extruder_multi_material_priming",
"activate_air_filtration",
"during_print_exhaust_fan_speed",
@@ -2595,7 +2596,7 @@ const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const
if (!is_step_done(psWipeTower) && filaments_cnt != 0) {
double width = m_config.prime_tower_width;
double layer_height = 0.2; // hard code layer height
if (m_config.purge_in_prime_tower) {
if (m_config.purge_in_prime_tower && m_config.single_extruder_multi_material) {
// Calculating depth 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.
@@ -2644,7 +2645,7 @@ void Print::_make_wipe_tower()
const auto bUseWipeTower2 = is_BBL_printer() ? false : true;
// Orca: itertate over wipe_volumes and change the non-zero values to the prime_volume
if (!m_config.purge_in_prime_tower && !is_BBL_printer()) {
if ((!m_config.purge_in_prime_tower || !m_config.single_extruder_multi_material) && !is_BBL_printer()) {
for (unsigned int i = 0; i < number_of_extruders; ++i) {
for (unsigned int j = 0; j < number_of_extruders; ++j) {
if (wipe_volumes[i][j] > 0) {
@@ -2823,7 +2824,7 @@ void Print::_make_wipe_tower()
if ((first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back()) || extruder_id !=
current_extruder_id) {
float volume_to_wipe = m_config.prime_volume;
if (m_config.purge_in_prime_tower) {
if (m_config.purge_in_prime_tower && m_config.single_extruder_multi_material) {
volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id]; // total volume to wipe after this toolchange
volume_to_wipe *= m_config.flush_multiplier;
// Not all of that can be used for infill purging:
+5
View File
@@ -1256,6 +1256,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER);
bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty());
bool model_origin_translation_differ = model_object.origin_translation != model_object_new.origin_translation;
bool brim_points_differ = model_brim_points_data_changed(model_object, model_object_new);
auto print_objects_range = print_object_status_db.get_range(model_object);
// The list actually can be empty if all instances are out of the print bed.
//assert(print_objects_range.begin() != print_objects_range.end());
@@ -1302,6 +1303,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
} else if (model_custom_seam_data_changed(model_object, model_object_new)) {
update_apply_status(this->invalidate_step(psGCodeExport));
}
if (brim_points_differ) {
model_object.brim_points = model_object_new.brim_points;
update_apply_status(this->invalidate_all_steps());
}
}
if (! solid_or_modifier_differ) {
// Synchronize Object's config.
+172 -39
View File
@@ -88,6 +88,7 @@ static t_config_enum_values s_keys_map_PrintHostType {
{ "obico", htObico },
{ "flashforge", htFlashforge },
{ "simplyprint", htSimplyPrint },
{ "elegoolink", htElegooLink }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType)
@@ -136,6 +137,7 @@ static t_config_enum_values s_keys_map_InfillPattern {
{ "concentric", ipConcentric },
{ "zig-zag", ipRectilinear },
{ "grid", ipGrid },
{ "2dlattice", ip2DLattice },
{ "line", ipLine },
{ "cubic", ipCubic },
{ "triangles", ipTriangles },
@@ -284,6 +286,14 @@ static t_config_enum_values s_keys_map_InternalBridgeFilter {
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InternalBridgeFilter)
static t_config_enum_values s_keys_map_EnableExtraBridgeLayer {
{ "disabled", eblDisabled },
{ "external_bridge_only", eblExternalBridgeOnly },
{ "internal_bridge_only", eblInternalBridgeOnly },
{ "apply_to_all", eblApplyToAll },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(EnableExtraBridgeLayer)
// Orca
static t_config_enum_values s_keys_map_GapFillTarget {
{ "everywhere", gftEverywhere },
@@ -318,6 +328,7 @@ static const t_config_enum_values s_keys_map_BrimType = {
{"outer_and_inner", btOuterAndInner},
{"auto_brim", btAutoBrim}, // BBS
{"brim_ears", btEar}, // Orca
{"painted", btPainted}, // BBS
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BrimType)
@@ -793,18 +804,18 @@ void PrintConfigDef::init_fff_params()
def->mode = comSimple;
def->enum_keys_map = &s_keys_map_BedType;
// Orca: make sure the order of the values is the same as the BedType enum
def->enum_values.emplace_back("Supertack Plate");
def->enum_values.emplace_back("Cool Plate");
def->enum_values.emplace_back("Engineering Plate");
def->enum_values.emplace_back("High Temp Plate");
def->enum_values.emplace_back("Textured PEI Plate");
def->enum_values.emplace_back("Textured Cool Plate");
def->enum_labels.emplace_back(L("Cool Plate (SuperTack)"));
def->enum_values.emplace_back("Supertack Plate");
def->enum_labels.emplace_back(L("Smooth Cool Plate"));
def->enum_labels.emplace_back(L("Engineering Plate"));
def->enum_labels.emplace_back(L("Smooth High Temp Plate"));
def->enum_labels.emplace_back(L("Textured PEI Plate"));
def->enum_labels.emplace_back(L("Textured Cool Plate"));
def->enum_labels.emplace_back(L("Cool Plate (SuperTack)"));
def->set_default_value(new ConfigOptionEnum<BedType>(btPC));
// BBS
@@ -908,15 +919,19 @@ void PrintConfigDef::init_fff_params()
def = this->add("enable_overhang_bridge_fan", coBools);
def->label = L("Force cooling for overhang and bridge");
def->tooltip = L("Enable this option to optimize part cooling fan speed for overhang and bridge to get better cooling");
def->label = L("Force cooling for overhangs and bridges");
def->tooltip = L("Enable this option to allow adjustment of the part cooling fan speed for specifically for overhangs, internal and external "
"bridges. Setting the fan speed specifically for these features can improve overall print quality and reduce warping.");
def->mode = comSimple;
def->set_default_value(new ConfigOptionBools{ true });
def = this->add("overhang_fan_speed", coInts);
def->label = L("Fan speed for overhang");
def->tooltip = L("Force part cooling fan to be this speed when printing bridge or overhang wall which has large overhang degree. "
"Forcing cooling for overhang and bridge can get better quality for these part");
def->label = L("Overhangs and external bridges fan speed");
def->tooltip = L("Use this part cooling fan speed when printing bridges or overhang walls with an overhang threshold that exceeds "
"the value set in the 'Overhangs cooling threshold' parameter above. Increasing the cooling specifically for overhangs "
"and bridges can improve the overall print quality of these features.\n\n"
"Please note, this fan speed is clamped on the lower end by the minimum fan speed threshold set above. It is also adjusted "
"upwards up to the maximum fan speed threshold when the minimum layer time threshold is not met.");
def->sidetext = L("%");
def->min = 0;
def->max = 100;
@@ -924,10 +939,11 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionInts { 100 });
def = this->add("overhang_fan_threshold", coEnums);
def->label = L("Cooling overhang threshold");
def->tooltip = L("Force cooling fan to be specific speed when overhang degree of printed part exceeds this value. "
"Expressed as percentage which indicates how much width of the line without support from lower layer. "
"0% means forcing cooling for all outer wall no matter how much overhang degree");
def->label = L("Overhang cooling activation threshold");
// xgettext:no-c-format, no-boost-format
def->tooltip = L("When the overhang exceeds this specified threshold, force the cooling fan to run at the 'Overhang Fan Speed' set below. "
"This threshold is expressed as a percentage, indicating the portion of each line's width that is unsupported by the layer "
"beneath it. Setting this value to 0% forces the cooling fan to run for all outer walls, regardless of the overhang degree.");
def->sidetext = "";
def->enum_keys_map = &ConfigOptionEnum<OverhangFanThreshold>::get_enum_values();
def->mode = comAdvanced;
@@ -946,8 +962,9 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionEnumsGeneric{ (int)Overhang_threshold_bridge });
def = this->add("bridge_angle", coFloat);
def->label = L("Bridge infill direction");
def->label = L("External bridge infill direction");
def->category = L("Strength");
// xgettext:no-c-format, no-boost-format
def->tooltip = L("Bridging angle override. If left to zero, the bridging angle will be calculated "
"automatically. Otherwise the provided angle will be used for external bridges. "
"Use 180°for zero angle.");
@@ -955,11 +972,39 @@ void PrintConfigDef::init_fff_params()
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.));
// ORCA: Internal bridge angle override
def = this->add("internal_bridge_angle", coFloat);
def->label = L("Internal bridge infill direction");
def->category = L("Strength");
def->tooltip = L("Internal bridging angle override. If left to zero, the bridging angle will be calculated "
"automatically. Otherwise the provided angle will be used for internal bridges. "
"Use 180°for zero angle.\n\nIt is recommended to leave it at 0 unless there is a specific model need not to.");
def->sidetext = L("°");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.));
def = this->add("bridge_density", coPercent);
def->label = L("Bridge density");
def->label = L("External bridge density");
def->category = L("Strength");
def->tooltip = L("Density of external bridges. 100% means solid bridge. Default is 100%.");
def->tooltip = L("Controls the density (spacing) of external bridge lines. 100% means solid bridge. Default is 100%.\n\n"
"Lower density external bridges can help improve reliability as there is more space for air to circulate "
"around the extruded bridge, improving its cooling speed.");
def->sidetext = L("%");
def->min = 10;
def->max = 100;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionPercent(100));
def = this->add("internal_bridge_density", coPercent);
def->label = L("Internal bridge density");
def->category = L("Strength");
def->tooltip = L("Controls the density (spacing) of internal bridge lines. 100% means solid bridge. Default is 100%.\n\n "
"Lower density internal bridges can help reduce top surface pillowing and improve internal bridge reliability as there is more space for "
"air to circulate around the extruded bridge, improving its cooling speed. \n\n"
"This option works particularly well when combined with the second internal bridge over infill option, "
"further improving internal bridging structure before solid infill is extruded.");
def->sidetext = L("%");
def->min = 10;
def->max = 100;
@@ -1216,12 +1261,14 @@ void PrintConfigDef::init_fff_params()
def->enum_keys_map = &ConfigOptionEnum<BrimType>::get_enum_values();
def->enum_values.emplace_back("auto_brim");
def->enum_values.emplace_back("brim_ears");
def->enum_values.emplace_back("painted");
def->enum_values.emplace_back("outer_only");
def->enum_values.emplace_back("inner_only");
def->enum_values.emplace_back("outer_and_inner");
def->enum_values.emplace_back("no_brim");
def->enum_labels.emplace_back(L("Auto"));
def->enum_labels.emplace_back(L("Mouse ear"));
def->enum_labels.emplace_back(L("Painted"));
def->enum_labels.emplace_back(L("Outer brim only"));
def->enum_labels.emplace_back(L("Inner brim only"));
def->enum_labels.emplace_back(L("Outer and inner brim"));
@@ -1415,7 +1462,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionBool(false));
def = this->add("thick_bridges", coBool);
def->label = L("Thick bridges");
def->label = L("Thick external bridges");
def->category = L("Quality");
def->tooltip = L("If enabled, bridges are more reliable, can bridge longer distances, but may look worse. "
"If disabled, bridges look better but are reliable just for shorter bridged distances.");
@@ -1429,23 +1476,53 @@ void PrintConfigDef::init_fff_params()
"consider turning it off if you are using large nozzles.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true));
def = this->add("enable_extra_bridge_layer", coEnum);
def->label = L("Extra bridge layers (beta)");
def->category = L("Quality");
def->tooltip = L("This option enables the generation of an extra bridge layer over internal and/or external bridges.\n\n"
"Extra bridge layers help improve bridge appearance and reliability, as the solid infill is better supported. "
"This is especially useful in fast printers, where the bridge and solid infill speeds vary greatly. "
"The extra bridge layer results in reduced pillowing on top surfaces, as well as reduced separation of the external bridge layer from its surrounding perimeters.\n\n"
"It is generally recommended to set this to at least 'External bridge only', unless specific issues with the sliced model are found.\n\n"
"Options:\n"
"1. Disabled - does not generate second bridge layers. This is the default and is set for compatibility purposes.\n"
"2. External bridge only - generates second bridge layers for external-facing bridges only. Please note that small bridges that are shorter "
"or narrower than the set number of perimeters will be skipped as they would not benefit from a second bridge layer. If generated, the second bridge layer will be extruded "
"parallel to the first bridge layer to reinforce the bridge strength.\n"
"3. Internal bridge only - generates second bridge layers for internal bridges over sparse infill only. Please note that the internal "
"bridges count towards the top shell layer count of your model. The second internal bridge layer will be extruded as close to perpendicular to the first as possible. If multiple regions "
"in the same island, with varying bridge angles are present, the last region of that island will be selected as the angle reference.\n"
"4. Apply to all - generates second bridge layers for both internal and external-facing bridges\n");
def->enum_keys_map = &ConfigOptionEnum<EnableExtraBridgeLayer>::get_enum_values();
def->enum_values.push_back("disabled");
def->enum_values.push_back("external_bridge_only");
def->enum_values.push_back("internal_bridge_only");
def->enum_values.push_back("apply_to_all");
def->enum_labels.push_back(L("Disabled"));
def->enum_labels.push_back(L("External bridge only"));
def->enum_labels.push_back(L("Internal bridge only"));
def->enum_labels.push_back(L("Apply to all"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<EnableExtraBridgeLayer>(eblDisabled));
def = this->add("dont_filter_internal_bridges", coEnum);
def->label = L("Filter out small internal bridges (beta)");
def->label = L("Filter out small internal bridges");
def->category = L("Quality");
def->tooltip = L("This option can help reducing pillowing on top surfaces in heavily slanted or curved models.\n\n"
"By default, small internal bridges are filtered out and the internal solid infill is printed directly"
" over the sparse infill. This works well in most cases, speeding up printing without too much compromise"
" on top surface quality. \n\nHowever, in heavily slanted or curved models especially where too low sparse"
" infill density is used, this may result in curling of the unsupported solid infill, causing pillowing.\n\n"
"Disabling this option will print internal bridge layer over slightly unsupported internal"
" solid infill. The options below control the amount of filtering, i.e. the amount of internal bridges "
def->tooltip = L("This option can help reduce pillowing on top surfaces in heavily slanted or curved models.\n\n"
"By default, small internal bridges are filtered out and the internal solid infill is printed directly "
"over the sparse infill. This works well in most cases, speeding up printing without too much compromise "
"on top surface quality. \n\nHowever, in heavily slanted or curved models, especially where too low a sparse "
"infill density is used, this may result in curling of the unsupported solid infill, causing pillowing.\n\n"
"Enabling limited filtering or no filtering will print internal bridge layer over slightly unsupported internal "
"solid infill. The options below control the sensitivity of the filtering, i.e. they control where internal bridges are "
"created.\n\n"
"Filter - enable this option. This is the default behavior and works well in most cases.\n\n"
"Limited filtering - creates internal bridges on heavily slanted surfaces, while avoiding creating "
"unnecessary internal bridges. This works well for most difficult models.\n\n"
"No filtering - creates internal bridges on every potential internal overhang. This option is useful "
"for heavily slanted top surface models. However, in most cases it creates too many unnecessary bridges.");
"1. Filter - enables this option. This is the default behavior and works well in most cases.\n\n"
"2. Limited filtering - creates internal bridges on heavily slanted surfaces while avoiding unnecessary bridges. "
"This works well for most difficult models.\n\n"
"3. No filtering - creates internal bridges on every potential internal overhang. This option is useful for "
"heavily slanted top surface models; however, in most cases, it creates too many unnecessary bridges.");
def->enum_keys_map = &ConfigOptionEnum<InternalBridgeFilter>::get_enum_values();
def->enum_values.push_back("disabled");
def->enum_values.push_back("limited");
@@ -2266,6 +2343,7 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("concentric");
def->enum_values.push_back("zig-zag");
def->enum_values.push_back("grid");
def->enum_values.push_back("2dlattice");
def->enum_values.push_back("line");
def->enum_values.push_back("cubic");
def->enum_values.push_back("triangles");
@@ -2285,6 +2363,7 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.push_back(L("Concentric"));
def->enum_labels.push_back(L("Rectilinear"));
def->enum_labels.push_back(L("Grid"));
def->enum_labels.push_back(L("2D Lattice"));
def->enum_labels.push_back(L("Line"));
def->enum_labels.push_back(L("Cubic"));
def->enum_labels.push_back(L("Triangles"));
@@ -2303,6 +2382,26 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.push_back(L("Quarter Cubic"));
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipCrossHatch));
def = this->add("lattice_angle_1", coFloat);
def->label = L("Lattice angle 1");
def->category = L("Strength");
def->tooltip = L("The angle of the first set of 2D lattice elements in the Z direction. Zero is vertical.");
def->sidetext = L("°");
def->min = -75;
def->max = 75;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(-45));
def = this->add("lattice_angle_2", coFloat);
def->label = L("Lattice angle 2");
def->category = L("Strength");
def->tooltip = L("The angle of the second set of 2D lattice elements in the Z direction. Zero is vertical.");
def->sidetext = L("°");
def->min = -75;
def->max = 75;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(45));
auto def_infill_anchor_min = def = this->add("infill_anchor", coFloatOrPercent);
def->label = L("Sparse infill anchor length");
def->category = L("Strength");
@@ -2470,7 +2569,7 @@ void PrintConfigDef::init_fff_params()
def->label = L("Top surface");
def->tooltip = L("Jerk for top surface");
def->sidetext = L("mm/s");
def->min = 1;
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(9));
@@ -2478,7 +2577,7 @@ void PrintConfigDef::init_fff_params()
def->label = L("Infill");
def->tooltip = L("Jerk for infill");
def->sidetext = L("mm/s");
def->min = 1;
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(9));
@@ -2486,7 +2585,7 @@ void PrintConfigDef::init_fff_params()
def->label = L("Initial layer");
def->tooltip = L("Jerk for initial layer");
def->sidetext = L("mm/s");
def->min = 1;
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(9));
@@ -2494,7 +2593,7 @@ void PrintConfigDef::init_fff_params()
def->label = L("Travel");
def->tooltip = L("Jerk for travel");
def->sidetext = L("mm/s");
def->min = 1;
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(12));
@@ -2586,15 +2685,27 @@ void PrintConfigDef::init_fff_params()
def = this->add("support_material_interface_fan_speed", coInts);
def->label = L("Support interface fan speed");
def->tooltip = L("This fan speed is enforced during all support interfaces, to be able to weaken their bonding with a high fan speed."
"\nSet to -1 to disable this override."
"\nCan only be overridden by disable_fan_first_layers.");
def->tooltip = L("This part cooling fan speed is applied when printing support interfaces. Setting this parameter to a higher than regular speed "
" reduces the layer binding strength between supports and the supported part, making them easier to separate."
"\nSet to -1 to disable it."
"\nThis setting is overridden by disable_fan_first_layers.");
def->sidetext = L("%");
def->min = -1;
def->max = 100;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInts{ -1 });
// ORCA: Add support for separate internal bridge fan speed control
def = this->add("internal_bridge_fan_speed", coInts);
def->label = L("Internal bridges fan speed");
def->tooltip = L("The part cooling fan speed used for all internal bridges. Set to -1 to use the overhang fan speed settings instead.\n\n"
"Reducing the internal bridges fan speed, compared to your regular fan speed, can help reduce part warping due to excessive "
"cooling applied over a large surface for a prolonged period of time.");
def->sidetext = L("%");
def->min = -1;
def->max = 100;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInts{ -1 });
def = this->add("fuzzy_skin", coEnum);
def->label = L("Fuzzy Skin");
@@ -2621,7 +2732,7 @@ void PrintConfigDef::init_fff_params()
def->min = 0;
def->max = 1;
def->mode = comSimple;
def->set_default_value(new ConfigOptionFloat(0.3));
def->set_default_value(new ConfigOptionFloat(0.2));
def = this->add("fuzzy_skin_point_distance", coFloat);
def->label = L("Fuzzy skin point distance");
@@ -2631,7 +2742,7 @@ void PrintConfigDef::init_fff_params()
def->min = 0;
def->max = 5;
def->mode = comSimple;
def->set_default_value(new ConfigOptionFloat(0.8));
def->set_default_value(new ConfigOptionFloat(0.3));
def = this->add("fuzzy_skin_first_layer", coBool);
def->label = L("Apply fuzzy skin to first layer");
@@ -3510,6 +3621,7 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("obico");
def->enum_values.push_back("flashforge");
def->enum_values.push_back("simplyprint");
def->enum_values.push_back("elegoolink");
def->enum_labels.push_back("PrusaLink");
def->enum_labels.push_back("PrusaConnect");
def->enum_labels.push_back("Octo/Klipper");
@@ -3523,6 +3635,7 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.push_back("Obico");
def->enum_labels.push_back("Flashforge");
def->enum_labels.push_back("SimplyPrint");
def->enum_labels.push_back("Elegoo Link");
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionEnum<PrintHostType>(htOctoPrint));
@@ -3835,7 +3948,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionInt {0});
def = this->add("long_retractions_when_cut", coBools);
def->label = L("Long retraction when cut(experimental)");
def->label = L("Long retraction when cut(beta)");
def->tooltip = L("Experimental feature.Retracting and cutting off the filament at a longer distance during changes to minimize purge."
"While this reduces flush significantly, it may also raise the risk of nozzle clogs or other printing problems.");
def->mode = comDevelop;
@@ -4316,6 +4429,26 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(200, true));
def = this->add("spiral_starting_flow_ratio", coFloat);
def->label = "Spiral starting flow ratio";
def->tooltip = L("Sets the starting flow ratio while transitioning from the last bottom layer to the spiral. "
"Normally the spiral transition scales the flow ratio from 0% to 100% during the first loop "
"which can in some cases lead to under extrusion at the start of the spiral.");
def->min = 0;
def->max = 1;
def->set_default_value(new ConfigOptionFloat(0));
def->mode = comAdvanced;
def = this->add("spiral_finishing_flow_ratio", coFloat);
def->label = "Spiral finishing flow ratio";
def->tooltip = L("Sets the finishing flow ratio while ending the spiral. "
"Normally the spiral transition scales the flow ratio from 100% to 0% during the last loop "
"which can in some cases lead to under extrusion at the end of the spiral.");
def->min = 0;
def->max = 1;
def->set_default_value(new ConfigOptionFloat(0));
def->mode = comAdvanced;
def = this->add("timelapse_type", coEnum);
def->label = L("Timelapse");
def->tooltip = L("If smooth or traditional mode is selected, a timelapse video will be generated for each print. "
@@ -6380,7 +6513,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
"retraction_distance_when_cut",
"extruder_type",
"internal_bridge_support_thickness","extruder_clearance_max_radius", "top_area_threshold", "reduce_wall_solid_infill","filament_load_time","filament_unload_time",
"smooth_coefficient", "overhang_totally_speed"
"smooth_coefficient", "overhang_totally_speed", "silent_mode"
};
if (ignore.find(opt_key) != ignore.end()) {
+18 -3
View File
@@ -50,7 +50,7 @@ enum class NoiseType {
};
enum PrintHostType {
htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htESP3D, htCrealityPrint, htObico, htFlashforge, htSimplyPrint
htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htESP3D, htCrealityPrint, htObico, htFlashforge, htSimplyPrint, htElegooLink
};
enum AuthorizationType {
@@ -58,7 +58,7 @@ enum AuthorizationType {
};
enum InfillPattern : int {
ipConcentric, ipRectilinear, ipGrid, ipLine, ipCubic, ipTriangles, ipStars, ipGyroid, ipHoneycomb, ipAdaptiveCubic, ipMonotonic, ipMonotonicLine, ipAlignedRectilinear, ip3DHoneycomb,
ipConcentric, ipRectilinear, ipGrid, ip2DLattice, ipLine, ipCubic, ipTriangles, ipStars, ipGyroid, ipHoneycomb, ipAdaptiveCubic, ipMonotonic, ipMonotonicLine, ipAlignedRectilinear, ip3DHoneycomb,
ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipSupportCubic, ipSupportBase, ipConcentricInternal,
ipLightning, ipCrossHatch, ipQuarterCubic,
ipCount,
@@ -187,6 +187,11 @@ enum InternalBridgeFilter {
ibfDisabled, ibfLimited, ibfNofilter
};
//Orca
enum EnableExtraBridgeLayer {
eblDisabled, eblExternalBridgeOnly, eblInternalBridgeOnly, eblApplyToAll
};
//Orca
enum GapFillTarget {
gftEverywhere, gftTopBottom, gftNowhere
@@ -221,6 +226,7 @@ enum SLAPillarConnectionMode {
enum BrimType {
btAutoBrim, // BBS
btEar, // Orca
btPainted, // BBS
btOuterOnly,
btInnerOnly,
btOuterAndInner,
@@ -262,12 +268,12 @@ enum OverhangFanThreshold {
// BBS
enum BedType {
btDefault = 0,
btSuperTack,
btPC,
btEP,
btPEI,
btPTE,
btPCT,
btSuperTack,
btCount
};
@@ -827,6 +833,9 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionBool, thick_bridges))
((ConfigOptionBool, thick_internal_bridges))
((ConfigOptionEnum<InternalBridgeFilter>, dont_filter_internal_bridges))
// Orca
((ConfigOptionEnum<EnableExtraBridgeLayer>, enable_extra_bridge_layer))
((ConfigOptionPercent, internal_bridge_density))
// Overhang angle threshold.
((ConfigOptionInt, support_threshold_angle))
((ConfigOptionFloatOrPercent, support_threshold_overlap))
@@ -907,6 +916,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionInt, bottom_shell_layers))
((ConfigOptionFloat, bottom_shell_thickness))
((ConfigOptionFloat, bridge_angle))
((ConfigOptionFloat, internal_bridge_angle)) // ORCA: Internal bridge angle override
((ConfigOptionFloat, bridge_flow))
((ConfigOptionFloat, internal_bridge_flow))
((ConfigOptionFloat, bridge_speed))
@@ -922,6 +932,8 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionBool, rotate_solid_infill_direction))
((ConfigOptionPercent, sparse_infill_density))
((ConfigOptionEnum<InfillPattern>, sparse_infill_pattern))
((ConfigOptionFloat, lattice_angle_1))
((ConfigOptionFloat, lattice_angle_2))
((ConfigOptionEnum<FuzzySkinType>, fuzzy_skin))
((ConfigOptionFloat, fuzzy_skin_thickness))
((ConfigOptionFloat, fuzzy_skin_point_distance))
@@ -1279,6 +1291,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionBool, spiral_mode))
((ConfigOptionBool, spiral_mode_smooth))
((ConfigOptionFloatOrPercent, spiral_mode_max_xy_smoothing))
((ConfigOptionFloat, spiral_finishing_flow_ratio))
((ConfigOptionFloat, spiral_starting_flow_ratio))
((ConfigOptionInt, standby_temperature_delta))
((ConfigOptionFloat, preheat_time))
((ConfigOptionInt, preheat_steps))
@@ -1331,6 +1345,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionBool, gcode_comments))
((ConfigOptionInt, slow_down_layers))
((ConfigOptionInts, support_material_interface_fan_speed))
((ConfigOptionInts, internal_bridge_fan_speed)) // ORCA: Add support for separate internal bridge fan speed control
// Orca: notes for profiles from PrusaSlicer
((ConfigOptionStrings, filament_notes))
((ConfigOptionString, notes))
+255 -4
View File
@@ -1077,8 +1077,10 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "rotate_solid_infill_direction"
|| opt_key == "ensure_vertical_shell_thickness"
|| opt_key == "bridge_angle"
|| opt_key == "internal_bridge_angle" // ORCA: Internal bridge angle override
//BBS
|| opt_key == "bridge_density") {
|| opt_key == "bridge_density"
|| opt_key == "internal_bridge_density") {
steps.emplace_back(posPrepareInfill);
} else if (
opt_key == "top_surface_pattern"
@@ -1089,7 +1091,9 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "infill_anchor_max"
|| opt_key == "top_surface_line_width"
|| opt_key == "initial_layer_line_width"
|| opt_key == "small_area_infill_flow_compensation") {
|| opt_key == "small_area_infill_flow_compensation"
|| opt_key == "lattice_angle_1"
|| opt_key == "lattice_angle_2") {
steps.emplace_back(posInfill);
} else if (opt_key == "sparse_infill_pattern") {
steps.emplace_back(posPrepareInfill);
@@ -1442,7 +1446,111 @@ void PrintObject::detect_surfaces_type()
for (size_t i = num_layers; i < m_layers.size(); ++ i)
m_layers[i]->m_regions[region_id]->slices.set_type(stInternal);
}
// ==================================================================================================
// === ORCA: Create a SECOND bridge layer above the first bridge layer. =============================
// === ORCA: Surface is flagged as a new surface type called stInternalAfterExternalBridge ==================
// === Algorithm only considers stInternal surfaces for re-classification, leaving stTop unaffected =
// ==================================================================================================
// Only iterate to the second-to-last layer, since we look at layer i+1.
if( (this->config().enable_extra_bridge_layer.value == eblApplyToAll) || (this->config().enable_extra_bridge_layer.value == eblExternalBridgeOnly)){
const size_t last = (m_layers.empty() ? 0 : m_layers.size() - 1);
tbb::parallel_for( tbb::blocked_range<size_t>(0, last), [this, region_id](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
m_print->throw_if_canceled();
// Step 1: Find bridge polygons
// Current layer (i): Search for stBottomBridge polygons.
const Surfaces &bot_surfs = m_layers[i]->m_regions[region_id]->slices.surfaces;
// Next layer (i+1): The layer where stInternal polygons may be re-classified.
Surfaces &top_surfs = m_layers[i + 1]->m_regions[region_id]->slices.surfaces;
// Step 2: Collect the bridge polygons in the current layer region
Polygons polygons_bridge;
for (const Surface &sbot : bot_surfs) {
if (sbot.surface_type == stBottomBridge) {
polygons_append(polygons_bridge, to_polygons(sbot));
}
}
// Step 3: Early termination of loop if no meaningfull bridge found
// No bridge polygons found, continue to the next layer
if (polygons_bridge.empty())
continue;
// Step 4: Bottom bridge polygons found - scan and create layer+1 bridge polygon
Surfaces new_surfaces;
new_surfaces.reserve(top_surfs.size());
//filtering parameters here. Filter bridges that are less than 2x external walls and 2xN internal perimeters wide.
LayerRegion *layerm = m_layers[i]->m_regions[region_id];
int number_of_internal_walls = std::max(0, layerm->m_region->config().wall_loops - 1); // number of internal walls, clamped to a minimum of 0 as a safety precaution
float offset_distance = layerm->flow(frExternalPerimeter).scaled_width() // shrink down by external perimeter width (effectively filtering out 2x external perimeters wide bridges)
+ ((layerm->flow(frPerimeter).scaled_width()) * number_of_internal_walls); // shrink down by number of external walls * width of them, effectively filtering out 2x internal perimeter wide bridges
// The reason for doing the above filtering is that in pure bridges, the walls are always printed separately as overhang walls. Here we care about the bridge infill which is distinct and is the remainder
// of the bridge area minus the perimeter width on both sides of the bridge itself.
// This would also skip generation of very short dual bridge layers (that are shorter than N perimeters), but these are unecessary as the bridge distance is
// We could reduce this slightly to account for innacurcies in the clipping operation.
// TODO: Monitor GitHub issues to check whether second bridge layers are ommited where they should be generated. If yes, reduce the filtering distance
// For each surface in the layer above
for (Surface &s_up : top_surfs) {
// Only reclassify stInternal polygons (i.e. what will become later solid and sparse infill)
// Leave the rest unaffected
if (s_up.surface_type != stInternal) {
new_surfaces.push_back(std::move(s_up)); // do not modify them
continue; // continue to the next surface
}
// Identify stInternal polygons that overlap with the bridging polygons on the layer underneath.
Polygons p_up = to_polygons(s_up);
ExPolygons overlap = intersection_ex(p_up, polygons_bridge , ApplySafetyOffset::Yes);
// Filter out the resulting candidate bridges based on size. First perform a shrink operation...
// ...followed by an expand operation to bring them back to the original size (positive offset)
overlap = offset_ex(shrink_ex(overlap, offset_distance), offset_distance);
// Now subtract the filtered new bridge layer from the remaining internal surfaces to create the new internal surface
ExPolygons remainder = diff_ex(p_up, overlap, ApplySafetyOffset::Yes);
// Remainder stays as stInternal
ExPolygons unified_remainder = union_safety_offset_ex(remainder);
for (auto &ex_remainder : unified_remainder) {
Surface s(stInternal, ex_remainder);
new_surfaces.push_back(std::move(s));
}
// Overlap portion becomes the new polygon type - stInternalAfterExternalBridge
ExPolygons unified_overlap = union_safety_offset_ex(overlap);
for (auto &ex_overlap : unified_overlap) {
Surface s(stInternalAfterExternalBridge, ex_overlap);
new_surfaces.push_back(std::move(s));
}
}
top_surfs = std::move(new_surfaces);
}
}
);
// ==============================================================================================================
// === ORCA: Interim workaround - for now the new stInternalAfterExternalBridge surfaace is re-classified ==============
// === back to a bottom bridge. As a starting point, this improves bridging reliability as it extrudes ==========
// === two external bridge layers. However, TODO: Implement a new surface type throughout the codebase ==========
// ==============================================================================================================
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) {
tbb::parallel_for( tbb::blocked_range<size_t>(0, m_layers.size()), [this, region_id](const tbb::blocked_range<size_t> &range) {
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) {
Surfaces &surfs = m_layers[idx_layer]->m_regions[region_id]->slices.surfaces;
for (Surface &s : surfs) {
if (s.surface_type == stInternalAfterExternalBridge) {
s.surface_type = stBottomBridge;
}
}
}
}
);
}
}
// ==============================================================================================================
// === ORCA: End of second external bridge layer changes =======================================================
// ==============================================================================================================
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - start";
// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
tbb::parallel_for(
@@ -2741,6 +2849,10 @@ void PrintObject::bridge_over_infill()
// Also, use Infill pattern that is neutral for angle determination, since there are no infill lines.
bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(boundary_plines), InfillPattern::ipLine, 0);
}
// ORCA: Internal bridge angle override
if (candidate.region->region().config().internal_bridge_angle > 0)
bridging_angle = candidate.region->region().config().internal_bridge_angle.value * PI / 180.0; // Convert degrees to radians
boundary_plines.insert(boundary_plines.end(), anchors.begin(), anchors.end());
if (!lightning_area.empty() && !intersection(area_to_be_bridge, lightning_area).empty()) {
@@ -2849,7 +2961,7 @@ void PrintObject::bridge_over_infill()
for (const ExPolygon &ep : new_internal_solids) {
new_surfaces.emplace_back(stInternalSolid, ep);
}
#ifdef DEBUG_BRIDGE_OVER_INFILL
debug_draw("Aensuring_" + std::to_string(reinterpret_cast<uint64_t>(&region)), to_polylines(additional_ensuring),
to_polylines(near_perimeters), to_polylines(to_polygons(internal_infills)),
@@ -2864,6 +2976,144 @@ void PrintObject::bridge_over_infill()
}
}
});
// ======================================================================================================================================
// === ORCA: Create a second internal bridge layer above the first bridge layer. ========================================================
// ======================================================================================================================================
if ( this->m_config.enable_extra_bridge_layer == eblApplyToAll || this->m_config.enable_extra_bridge_layer == eblInternalBridgeOnly) {
// Process layers in parallel up to second-to-last
tbb::parallel_for( tbb::blocked_range<size_t>(0, this->layers().size() - 1), [this](const tbb::blocked_range<size_t>& r) {
for (size_t lidx = r.begin(); lidx < r.end(); ++lidx)
{
Layer* layer = this->get_layer(lidx);
// (A) Gather internal bridging surfaces in the current layer
ExPolygons bridging_current_layer;
double bridging_angle_current = 0.0;
bool found_any_bridge = false;
float offset_distance = 0.0f;
// Pick a region from which to retrieve the flow width
if (!layer->regions().empty())
offset_distance = layer->regions().front()->flow(frSolidInfill).scaled_width();
for (LayerRegion *region : layer->regions()) {
for (const Surface &surf : region->fill_surfaces.surfaces) {
if (surf.surface_type == stInternalBridge) {
bridging_current_layer.push_back(surf.expolygon);
bridging_angle_current = surf.bridge_angle; // Store the last bridging angle of the current print object
found_any_bridge = true;
}
}
}
// If no bridging in this layer, continue with the next
if (!found_any_bridge || bridging_current_layer.empty())
continue;
// (B) Shrink-expand to remove trivial bridging areas
bridging_current_layer = offset_ex( shrink_ex(bridging_current_layer, offset_distance), offset_distance );
if (bridging_current_layer.empty())
continue; // all bridging was trivial, continue with the next layer
// (C) If there is a next layer, identify overlapping stInternal & stInternalSolid areas and convert the overlap to stSecondInternalBridge
if (lidx + 1 < this->layers().size()) {
Layer* next_layer = this->get_layer(lidx + 1);
// second bridging angle is 90 degrees offset
double bridging_angle_second = bridging_angle_current + M_PI / 2.0;
// Union the bridging polygons
ExPolygons bridging_union = union_safety_offset_ex(bridging_current_layer);
for (LayerRegion *next_region : next_layer->regions()) {
Surfaces next_new_surfaces;
Surfaces keep_surfaces;
// 1) Do not modify (keep) anything that isn't stInternal or stInternalSolid
for (const Surface &s : next_region->fill_surfaces.surfaces) {
if ( (s.surface_type != stInternal) && (s.surface_type != stInternalSolid)) {
keep_surfaces.push_back(s);
}
}
// 2) For stInternal & stInternalSolid surfaces, check if they overlap bridging_union
// 2a) Gather the next internal stInternalSolid surfaces first
SurfacesPtr next_internals = next_region->fill_surfaces.filter_by_types({ stInternal, stInternalSolid });
// 2b) For every collected next stInternalSolid surface
for (const Surface *s : next_internals) {
// Intersect it with the current layer bridging polygons
ExPolygons overlap = intersection_ex( s->expolygon, bridging_union, ApplySafetyOffset::Yes );
// Shrink + expand to remove trivial polygons
overlap = offset_ex(shrink_ex(overlap, offset_distance), offset_distance);
// Overlapping portion found -> this will become the second internal bridge
if (!overlap.empty()) {
// Create second bridge surface
Surface tmp{*s, {}};
tmp.surface_type = stSecondInternalBridge;
tmp.bridge_angle = bridging_angle_second;
// Insert bridging polygons
for (const ExPolygon &ep : overlap) {
next_new_surfaces.emplace_back(tmp, ep);
}
// Calculate leftover polygons = s->expolygon - bridging_union
ExPolygons leftover = diff_ex(s->expolygon, bridging_union, ApplySafetyOffset::Yes);
// Shrink + expand to remove trivial polygons
leftover = offset_ex(shrink_ex(leftover, offset_distance), offset_distance);
// Leftover polygons exist. Add them to the new surface maintaining their original attributes
if (!leftover.empty()) {
ExPolygons unified_leftover = union_safety_offset_ex(leftover);
for (const ExPolygon &ep : unified_leftover) {
// keep same type / angle as original
Surface leftover_surf{*s, {}};
leftover_surf.surface_type = s->surface_type;
leftover_surf.bridge_angle = s->bridge_angle;
next_new_surfaces.emplace_back(leftover_surf, ep);
}
}
}
else { // No overlapping portion found
// keep the surface intact
keep_surfaces.push_back(*s);
}
}
// 3) Rebuild next_region surfaces
next_region->fill_surfaces.surfaces.clear();
next_region->fill_surfaces.append(keep_surfaces);
next_region->fill_surfaces.append(next_new_surfaces);
} // end for next_layer->regions
} // end if next layer
}
}); // end parallel_for
// =================================================================================================================
// === ORCA: Interim workaround - for now the new stSecondInternalBridge surfaces are re-classified ===============
// === back to an internal bridge. As a starting point, this improves bridging reliability as it extrudes ==========
// === two external bridge layers. However, TODO: Implement a new surface type throughout the codebase =============
// =================================================================================================================
for (size_t lidx = 0; lidx < this->layers().size(); ++lidx) {
Layer* layer = this->get_layer(lidx);
for (LayerRegion* region : layer->regions()) {
for (Surface &surf : region->fill_surfaces.surfaces) {
if (surf.surface_type == stSecondInternalBridge) {
surf.surface_type = stInternalBridge;
}
}
}
}
}
// ===========================================================================================
// === ORCA: End of second bridging pass =====================================================
// ===========================================================================================
BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info();
@@ -3497,6 +3747,7 @@ void PrintObject::combine_infill()
((infill_pattern == ipRectilinear ||
infill_pattern == ipMonotonic ||
infill_pattern == ipGrid ||
infill_pattern == ip2DLattice ||
infill_pattern == ipLine ||
infill_pattern == ipHoneycomb) ? 1.5f : 0.5f) *
layerms.back()->flow(frSolidInfill).scaled_width();
+4
View File
@@ -13,12 +13,16 @@ enum SurfaceType {
stBottom,
// Bottom horizontal surface, visible from the bottom, unsupported, printed with a bridging extrusion flow.
stBottomBridge,
// Second bridge surface above a bottom bridge.
stInternalAfterExternalBridge,
// Normal sparse infill.
stInternal,
// Full infill, supporting the top surfaces and/or defining the verticall wall thickness.
stInternalSolid,
// 1st layer of dense infill over sparse infill, printed with a bridging extrusion flow.
stInternalBridge,
// 2nd layer of dense infill over sparse infill, printed with a bridging extrusion flow.
stSecondInternalBridge,
// stInternal turns into void surfaces if the sparse infill is used for supports only,
// or if sparse infill layers get combined into a single layer.
stInternalVoid,
+1 -2
View File
@@ -65,9 +65,8 @@ static constexpr double LARGE_BED_THRESHOLD = 2147;
static constexpr size_t MAXIMUM_EXTRUDER_NUMBER = 64;
extern double SCALING_FACTOR;
// for creating circles (for brim_ear)
#define POLY_SIDES 24
static constexpr double PI = 3.141592653589793238;
#define POLY_SIDE_COUNT 24 // for brim ear circle
// When extruding a closed loop, the loop is interrupted and shortened a bit to reduce the seam.
// SoftFever: replaced by seam_gap now
// static constexpr double LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER = 0.15;
+1 -1
View File
@@ -5,7 +5,7 @@ Icon=OrcaSlicer
Exec=orca-slicer %U
Terminal=false
Type=Application
MimeType=model/stl;model/3mf;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;
MimeType=model/stl;model/3mf;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;x-scheme-handler/orcaslicer;
Categories=Graphics;3DGraphics;Engineering;
Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA
StartupNotify=false
+5
View File
@@ -145,6 +145,8 @@ set(SLIC3R_GUI_SOURCES
#GUI/Gizmos/GLGizmoFaceDetector.hpp
GUI/Gizmos/GLGizmoMeasure.cpp
GUI/Gizmos/GLGizmoMeasure.hpp
GUI/Gizmos/GLGizmoBrimEars.cpp
GUI/Gizmos/GLGizmoBrimEars.hpp
GUI/Gizmos/GLGizmoAssembly.cpp
GUI/Gizmos/GLGizmoAssembly.hpp
GUI/Gizmos/GLGizmoSeam.cpp
@@ -567,6 +569,9 @@ set(SLIC3R_GUI_SOURCES
GUI/Jobs/OAuthJob.hpp
Utils/SimplyPrint.cpp
Utils/SimplyPrint.hpp
Utils/ElegooLink.hpp
Utils/ElegooLink.cpp
Utils/WebSocketClient.hpp
)
if (WIN32)
+1 -1
View File
@@ -258,7 +258,7 @@ void AMSMaterialsSetting::create_panel_normal(wxWindow* parent)
m_panel_SN->Fit();
wxBoxSizer* m_tip_sizer = new wxBoxSizer(wxHORIZONTAL);
m_tip_readonly = new Label(parent, _L(""));
m_tip_readonly = new Label(parent, L"");
m_tip_readonly->SetForegroundColour(*wxBLACK);
m_tip_readonly->SetBackgroundColour(*wxWHITE);
m_tip_readonly->SetMinSize(wxSize(FromDIP(380), -1));
+19 -11
View File
@@ -527,19 +527,22 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
bool has_spiral_vase = config->opt_bool("spiral_mode");
toggle_line("spiral_mode_smooth", has_spiral_vase);
toggle_line("spiral_mode_max_xy_smoothing", has_spiral_vase && config->opt_bool("spiral_mode_smooth"));
bool has_top_solid_infill = config->opt_int("top_shell_layers") > 0;
bool has_bottom_solid_infill = config->opt_int("bottom_shell_layers") > 0;
bool has_solid_infill = has_top_solid_infill || has_bottom_solid_infill;
// solid_infill_filament uses the same logic as in Print::extruders()
for (auto el : { "top_surface_pattern", "bottom_surface_pattern", "internal_solid_infill_pattern", "solid_infill_filament"})
toggle_field(el, has_solid_infill);
toggle_line("spiral_starting_flow_ratio", has_spiral_vase);
toggle_line("spiral_finishing_flow_ratio", has_spiral_vase);
bool has_top_shell = config->opt_int("top_shell_layers") > 0;
bool has_bottom_shell = config->opt_int("bottom_shell_layers") > 0;
bool has_solid_infill = has_top_shell || has_bottom_shell;
toggle_field("top_surface_pattern", has_top_shell);
toggle_field("bottom_surface_pattern", has_bottom_shell);
for (auto el : { "infill_direction", "sparse_infill_line_width",
"sparse_infill_speed", "bridge_speed", "internal_bridge_speed", "bridge_angle","solid_infill_direction", "rotate_solid_infill_direction" })
"sparse_infill_speed", "bridge_speed", "internal_bridge_speed", "bridge_angle","internal_bridge_angle",
"solid_infill_direction", "rotate_solid_infill_direction", "internal_solid_infill_pattern", "solid_infill_filament",
})
toggle_field(el, have_infill || has_solid_infill);
toggle_field("top_shell_thickness", ! has_spiral_vase && has_top_solid_infill);
toggle_field("bottom_shell_thickness", ! has_spiral_vase && has_bottom_solid_infill);
toggle_field("top_shell_thickness", ! has_spiral_vase && has_top_shell);
toggle_field("bottom_shell_thickness", ! has_spiral_vase && has_bottom_shell);
toggle_field("wall_direction", !has_spiral_vase);
@@ -547,7 +550,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
toggle_field("gap_infill_speed", have_perimeters);
for (auto el : { "top_surface_line_width", "top_surface_speed" })
toggle_field(el, has_top_solid_infill || (has_spiral_vase && has_bottom_solid_infill));
toggle_field(el, has_top_shell || (has_spiral_vase && has_bottom_shell));
bool have_default_acceleration = config->opt_float("default_acceleration") > 0;
@@ -567,7 +570,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
bool have_brim = (config->opt_enum<BrimType>("brim_type") != btNoBrim);
toggle_field("brim_object_gap", have_brim);
bool have_brim_width = (config->opt_enum<BrimType>("brim_type") != btNoBrim) && config->opt_enum<BrimType>("brim_type") != btAutoBrim;
bool have_brim_width = (config->opt_enum<BrimType>("brim_type") != btNoBrim) && config->opt_enum<BrimType>("brim_type") != btAutoBrim &&
config->opt_enum<BrimType>("brim_type") != btPainted;
toggle_field("brim_width", have_brim_width);
// wall_filament uses the same logic as in Print::extruders()
toggle_field("wall_filament", have_perimeters || have_brim);
@@ -782,6 +786,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
toggle_line("interlocking_beam_layer_count", use_beam_interlocking);
toggle_line("interlocking_depth", use_beam_interlocking);
toggle_line("interlocking_boundary_avoidance", use_beam_interlocking);
bool lattice_options = config->opt_enum<InfillPattern>("sparse_infill_pattern") == InfillPattern::ip2DLattice;
for (auto el : { "lattice_angle_1", "lattice_angle_2"})
toggle_line(el, lattice_options);
}
void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/)
+5
View File
@@ -1,4 +1,5 @@
#include "CreatePresetsDialog.hpp"
#include <boost/log/trivial.hpp>
#include <vector>
#include <set>
#include <unordered_map>
@@ -4240,6 +4241,10 @@ void ExportConfigsDialog::data_init()
Preset *new_filament_preset = new Preset(filament_preset);
const Preset *base_filament_preset = preset_bundle.filaments.get_preset_base(*new_filament_preset);
if (base_filament_preset == nullptr) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to find base preset";
continue;
}
std::string filament_preset_name = base_filament_preset->name;
std::string machine_name = get_machine_name(filament_preset_name);
m_filament_name_to_presets[get_filament_name(filament_preset_name)].push_back(std::make_pair(get_vendor_name(machine_name), new_filament_preset));
+24 -28
View File
@@ -1441,6 +1441,8 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
&& !vol->is_modifier) {
vol->force_neutral_color = true;
}
else if (gizmo_type == GLGizmosManager::BrimEars)
vol->force_neutral_color = false;
else if (gizmo_type == GLGizmosManager::MmuSegmentation)
vol->is_active = false;
else
@@ -1539,15 +1541,22 @@ void GLCanvas3D::refresh_camera_scene_box()
BoundingBoxf3 GLCanvas3D::volumes_bounding_box(bool current_plate_only) const
{
BoundingBoxf3 bb;
PartPlate *plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
BoundingBoxf3 expand_part_plate_list_box;
bool is_limit = m_canvas_type != ECanvasType::CanvasAssembleView;
if (is_limit) {
auto plate_list_box = current_plate_only ? wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_bounding_box() :
wxGetApp().plater()->get_partplate_list().get_bounding_box();
auto horizontal_radius = 0.5 * sqrt(std::pow(plate_list_box.min[0] - plate_list_box.max[0], 2) + std::pow(plate_list_box.min[1] - plate_list_box.max[1], 2));
const float scale = 2;
expand_part_plate_list_box.merge(plate_list_box.min - scale * Vec3d(horizontal_radius, horizontal_radius, 0));
expand_part_plate_list_box.merge(plate_list_box.max + scale * Vec3d(horizontal_radius, horizontal_radius, 0));
}
for (const GLVolume *volume : m_volumes.volumes) {
if (!m_apply_zoom_to_volumes_filter || ((volume != nullptr) && volume->zoom_to_volumes)) {
const auto plate_bb = plate->get_bounding_box();
const auto v_bb = volume->transformed_bounding_box();
if (!plate_bb.overlap(v_bb))
continue;
bb.merge(v_bb);
const auto v_bb = volume->transformed_bounding_box();
if (is_limit && !expand_part_plate_list_box.overlap(v_bb))
continue;
bb.merge(v_bb);
}
}
return bb;
@@ -1893,6 +1902,7 @@ void GLCanvas3D::render(bool only_init)
//BBS add partplater rendering logic
bool only_current = false, only_body = false, show_axes = true, no_partplate = false;
bool show_grid = true;
GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type();
if (!m_main_toolbar.is_enabled()) {
//only_body = true;
@@ -1900,6 +1910,8 @@ void GLCanvas3D::render(bool only_init)
}
else if ((gizmo_type == GLGizmosManager::FdmSupports) || (gizmo_type == GLGizmosManager::Seam) || (gizmo_type == GLGizmosManager::MmuSegmentation))
no_partplate = true;
else if (gizmo_type == GLGizmosManager::BrimEars && !camera.is_looking_downward())
show_grid = false;
/* view3D render*/
int hover_id = (m_hover_plate_idxs.size() > 0)?m_hover_plate_idxs.front():-1;
@@ -1911,7 +1923,7 @@ void GLCanvas3D::render(bool only_init)
if (!no_partplate)
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), show_axes);
if (!no_partplate) //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);
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid);
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
}
/* preview render */
@@ -7162,9 +7174,9 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d&
m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_axes);
}
void GLCanvas3D::_render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali)
void GLCanvas3D::_render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali, bool show_grid)
{
wxGetApp().plater()->get_partplate_list().render(view_matrix, projection_matrix, bottom, only_current, only_body, hover_id, render_cali);
wxGetApp().plater()->get_partplate_list().render(view_matrix, projection_matrix, bottom, only_current, only_body, hover_id, render_cali, show_grid);
}
void GLCanvas3D::_render_plane() const
@@ -7431,7 +7443,7 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale()
return;
}
float scale = wxGetApp().toolbar_icon_scale();
float scale = wxGetApp().toolbar_icon_scale() * get_scale();
Size cnv_size = get_canvas_size();
//BBS: GUI refactor: GLToolbar
@@ -7442,26 +7454,12 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale()
float size = size_i;
// Set current size for all top toolbars. It will be used for next calculations
#if ENABLE_RETINA_GL
const float sc = m_retina_helper->get_scale_factor() * scale;
//BBS: GUI refactor: GLToolbar
m_main_toolbar.set_scale(sc);
m_assemble_view_toolbar.set_scale(sc);
m_separator_toolbar.set_scale(sc);
collapse_toolbar.set_scale(sc / 2.0);
size *= m_retina_helper->get_scale_factor();
auto* m_notification = wxGetApp().plater()->get_notification_manager();
m_notification->set_scale(sc);
m_gizmos.set_overlay_scale(sc);
#else
//BBS: GUI refactor: GLToolbar
m_main_toolbar.set_icons_size(size);
m_assemble_view_toolbar.set_icons_size(size);
m_separator_toolbar.set_icons_size(size);
collapse_toolbar.set_icons_size(size / 2.0);
m_gizmos.set_overlay_icon_size(size);
#endif // ENABLE_RETINA_GL
//BBS: GUI refactor: GLToolbar
#if BBS_TOOLBAR_ON_TOP
@@ -7498,9 +7496,7 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale()
// set minimum scale as a auto scale for the toolbars
float new_scale = std::min(new_h_scale, new_v_scale);
#if ENABLE_RETINA_GL
new_scale /= m_retina_helper->get_scale_factor();
#endif
new_scale /= get_scale();
if (fabs(new_scale - scale) > 0.05) // scale is changed by 5% and more
wxGetApp().set_auto_toolbar_icon_scale(new_scale);
}
+1 -1
View File
@@ -1161,7 +1161,7 @@ private:
void _render_background();
void _render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes);
//BBS: add part plate related logic
void _render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body = false, int hover_id = -1, bool render_cali = false);
void _render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body = false, int hover_id = -1, bool render_cali = false, bool show_grid = true);
//BBS: add outline drawing logic
void _render_objects(GLVolumeCollection::ERenderType type, bool with_outline = true);
//BBS: GUI refactor: add canvas size as parameters
+2 -2
View File
@@ -106,8 +106,8 @@ std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::PART_CAT
}},
{ L("Strength"), {{"wall_loops", "",1},{"top_shell_layers", L("Top Solid Layers"),1},{"top_shell_thickness", L("Top Minimum Shell Thickness"),1},
{"bottom_shell_layers", L("Bottom Solid Layers"),1}, {"bottom_shell_thickness", L("Bottom Minimum Shell Thickness"),1},
{"sparse_infill_density", "",1},{"sparse_infill_pattern", "",1},{"infill_anchor", "",1},{"infill_anchor_max", "",1},{"top_surface_pattern", "",1},{"bottom_surface_pattern", "",1}, {"internal_solid_infill_pattern", "",1},
{"infill_combination", "",1}, {"infill_combination_max_layer_height", "",1}, {"infill_wall_overlap", "",1},{"top_bottom_infill_wall_overlap", "",1}, {"solid_infill_direction", "",1}, {"rotate_solid_infill_direction", "",1}, {"infill_direction", "",1}, {"bridge_angle", "",1}, {"minimum_sparse_infill_area", "",1}
{"sparse_infill_density", "",1},{"sparse_infill_pattern", "",1},{"lattice_angle_1", "",1},{"lattice_angle_2", "",1},{"infill_anchor", "",1},{"infill_anchor_max", "",1},{"top_surface_pattern", "",1},{"bottom_surface_pattern", "",1}, {"internal_solid_infill_pattern", "",1},
{"infill_combination", "",1}, {"infill_combination_max_layer_height", "",1}, {"infill_wall_overlap", "",1},{"top_bottom_infill_wall_overlap", "",1}, {"solid_infill_direction", "",1}, {"rotate_solid_infill_direction", "",1}, {"infill_direction", "",1}, {"bridge_angle", "",1}, {"internal_bridge_angle", "",1}, {"minimum_sparse_infill_area", "",1}
}},
{ L("Speed"), {{"outer_wall_speed", "",1},{"inner_wall_speed", "",2},{"sparse_infill_speed", "",3},{"top_surface_speed", "",4}, {"internal_solid_infill_speed", "",5},
{"enable_overhang_speed", "",6}, {"overhang_speed_classic", "",6}, {"overhang_1_4_speed", "",7}, {"overhang_2_4_speed", "",8}, {"overhang_3_4_speed", "",9}, {"overhang_4_4_speed", "",10},
+4 -4
View File
@@ -74,11 +74,11 @@ PickingModel GLGizmoBase::Grabber::s_cone;
GLGizmoBase::Grabber::~Grabber()
{
if (s_cube.model.is_initialized())
s_cube.model.reset();
//if (s_cube.model.is_initialized())
// s_cube.model.reset();
if (s_cone.model.is_initialized())
s_cone.model.reset();
//if (s_cone.model.is_initialized())
// s_cone.model.reset();
}
float GLGizmoBase::Grabber::get_half_size(float size) const
+1
View File
@@ -32,6 +32,7 @@ class ImGuiWrapper;
class GLCanvas3D;
enum class CommonGizmosDataID;
class CommonGizmosDataPool;
class Selection;
class GLGizmoBase
{
File diff suppressed because it is too large Load Diff
+180
View File
@@ -0,0 +1,180 @@
#ifndef slic3r_GLGizmoBrimEars_hpp_
#define slic3r_GLGizmoBrimEars_hpp_
#include "GLGizmoBase.hpp"
#include "slic3r/GUI/GLSelectionRectangle.hpp"
#include "libslic3r/BrimEarsPoint.hpp"
#include "libslic3r/ObjectID.hpp"
namespace Slic3r {
class ConfigOption;
namespace GUI {
enum class SLAGizmoEventType : unsigned char;
class GLGizmoBrimEars : public GLGizmoBase
{
private:
using PickRaycaster = SceneRaycasterItem;
bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal);
bool unproject_on_mesh2(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal);
const float RenderPointScale = 1.f;
class CacheEntry {
public:
CacheEntry() :
brim_point(BrimPoint()),
selected(false),
normal(Vec3f(0, 0, 1)),
is_hover(false),
is_error(false)
{}
CacheEntry(const BrimPoint &point, bool sel = false, const Vec3f &norm = Vec3f(0, 0, 1), bool hover = false, bool error = false)
: brim_point(point), selected(sel), normal(norm), is_hover(hover), is_error(error)
{}
bool operator==(const CacheEntry& rhs) const {
return (brim_point == rhs.brim_point);
}
bool operator!=(const CacheEntry& rhs) const {
return ! ((*this) == rhs);
}
inline bool pos_is_zero() {
return brim_point.pos.isZero();
}
void set_empty() {
brim_point = BrimPoint();
selected = false;
normal.setZero();
is_hover = false;
is_error = false;
}
BrimPoint brim_point;
bool selected; // whether the point is selected
bool is_hover; // show mouse hover cylinder
bool is_error;
Vec3f normal;
template<class Archive>
void serialize(Archive & ar)
{
ar(brim_point, selected, normal);
}
};
public:
GLGizmoBrimEars(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
virtual ~GLGizmoBrimEars() = default;
void data_changed(bool is_serializing) override;
void set_brim_data();
bool on_mouse(const wxMouseEvent& mouse_event) override;
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
void delete_selected_points();
void save_model();
//ClippingPlane get_sla_clipping_plane() const;
bool is_selection_rectangle_dragging() const { return m_selection_rectangle.is_dragging(); }
bool wants_enter_leave_snapshots() const override { return true; }
std::string get_gizmo_entering_text() const override { return "Entering Brim Ears"; }
std::string get_gizmo_leaving_text() const override { return "Leaving Brim Ears"; }
private:
bool on_init() override;
void on_dragging(const UpdateData& data) override;
void on_render() override;
void render_points(const Selection& selection);
float m_new_point_head_diameter; // Size of a new point.
float m_max_angle = 125.f;
float m_detection_radius = 1.f;
double m_detection_radius_max = .0f;
CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited
float m_old_point_head_diameter = 0.; // the same
mutable std::vector<CacheEntry> m_editing_cache; // a support point and whether it is currently selectedchanges or undo/redo
std::map<int, CacheEntry> m_single_brim;
ObjectID m_old_mo_id;
const Vec3d m_world_normal = {0, 0, 1};
std::map<GLVolume*, std::shared_ptr<PickRaycaster>> m_mesh_raycaster_map;
GLVolume* m_last_hit_volume;
CacheEntry* render_hover_point = nullptr;
bool m_link_text_hover = false;
PickingModel m_cylinder;
// This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
std::map<std::string, wxString> m_desc;
GLSelectionRectangle m_selection_rectangle;
ExPolygons m_first_layer;
bool m_wait_for_up_event = false;
bool m_selection_empty = true;
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
bool is_mesh_point_clipped(const Vec3d& point) const;
// Methods that do the model_object and editing cache synchronization,
// editing mode selection, etc:
enum {
AllPoints = -2,
NoPoints,
};
void select_point(int i);
void unselect_point(int i);
void reload_cache();
Points generate_points(Polygon &obj_polygon, float ear_detection_length, float brim_ears_max_angle, bool is_outer);
void auto_generate();
void first_layer_slicer();
void get_detection_radius_max();
void update_raycasters();
protected:
void on_set_state() override;
void on_set_hover_id() override
{
if ((int)m_editing_cache.size() <= m_hover_id)
m_hover_id = -1;
}
void on_start_dragging() override;
void on_stop_dragging() override;
void on_render_input_window(float x, float y, float bottom_limit) override;
void show_tooltip_information(float x, float y);
std::string on_get_name() const override;
bool on_is_activable() const override;
//bool on_is_selectable() const override;
virtual CommonGizmosDataID on_get_requirements() const override;
void on_load(cereal::BinaryInputArchive& ar) override;
void on_save(cereal::BinaryOutputArchive& ar) const override;
virtual void on_register_raycasters_for_picking() override;
virtual void on_unregister_raycasters_for_picking() override;
void register_single_mesh_pick();
//void update_single_mesh_pick(GLVolume* v);
void reset_all_pick();
bool add_point_to_cache(Vec3f pos, float head_radius, bool selected, Vec3f normal);
float get_brim_default_radius() const;
ExPolygon make_polygon(BrimPoint point, const Geometry::Transformation &trsf);
void find_single();
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoBrimEars_hpp_
+15 -5
View File
@@ -312,6 +312,11 @@ void ObjectClipper::render_cut(const std::vector<size_t>* ignore_idxs) const
}
}
void ObjectClipper::set_position_to_init_layer()
{
m_clp.reset(new ClippingPlane({0, 0, 1}, 0.1));
get_pool()->get_canvas()->set_as_dirty();
}
int ObjectClipper::get_number_of_contours() const
{
@@ -351,17 +356,22 @@ std::vector<Vec3d> ObjectClipper::point_per_contour() const
}
void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal)
void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal, bool vertical_normal)
{
const ModelObject* mo = get_pool()->selection_info()->model_object();
int active_inst = get_pool()->selection_info()->get_active_instance();
double z_shift = get_pool()->selection_info()->get_sla_shift();
//Vec3d camera_dir = wxGetApp().plater()->get_camera().get_dir_forward();
//if (abs(camera_dir(0)) > EPSILON || abs(camera_dir(1)) > EPSILON)
// camera_dir(2) = 0;
Vec3d normal;
if(vertical_normal) {
normal = {0, 0, 1};
}else {
//Vec3d camera_dir = wxGetApp().plater()->get_camera().get_dir_forward();
//if (abs(camera_dir(0)) > EPSILON || abs(camera_dir(1)) > EPSILON)
// camera_dir(2) = 0;
Vec3d normal = (keep_normal && m_clp) ? m_clp->get_normal() : /*-camera_dir;*/ -wxGetApp().plater()->get_camera().get_dir_forward();
normal = (keep_normal && m_clp) ? m_clp->get_normal() : /*-camera_dir;*/ -wxGetApp().plater()->get_camera().get_dir_forward();
}
Vec3d center;
if (get_pool()->get_canvas()->get_canvas_type() == GLCanvas3D::CanvasAssembleView) {
+2 -1
View File
@@ -232,9 +232,10 @@ public:
CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; }
#endif // NDEBUG
double get_position() const { return m_clp_ratio; }
void set_position_to_init_layer();
const ClippingPlane* get_clipping_plane(bool ignore_hide_clipped = false) const;
void render_cut(const std::vector<size_t>* ignore_idxs = nullptr) const;
void set_position_by_ratio(double pos, bool keep_normal);
void set_position_by_ratio(double pos, bool keep_normal, bool vertical_normal=false);
void set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos);
void set_behavior(bool hide_clipped, bool fill_cut, double contour_width);
+29 -12
View File
@@ -15,6 +15,7 @@
#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
//#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
//#include "slic3r/GUI/Gizmos/GLGizmoFaceDetector.hpp"
//#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
@@ -206,6 +207,7 @@ bool GLGizmosManager::init()
m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, m_is_dark ? "toolbar_measure_dark.svg" : "toolbar_measure.svg", EType::Measure));
m_gizmos.emplace_back(new GLGizmoAssembly(m_parent, m_is_dark ? "toolbar_assembly_dark.svg" : "toolbar_assembly.svg", EType::Assembly));
m_gizmos.emplace_back(new GLGizmoSimplify(m_parent, "reduce_triangles.svg", EType::Simplify));
m_gizmos.emplace_back(new GLGizmoBrimEars(m_parent, m_is_dark ? "toolbar_brimears_dark.svg" : "toolbar_brimears.svg", EType::BrimEars));
//m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", sprite_id++));
//m_gizmos.emplace_back(new GLGizmoFaceDetector(m_parent, "face recognition.svg", sprite_id++));
//m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", sprite_id++));
@@ -453,6 +455,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
return dynamic_cast<GLGizmoCut3D*>(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == MeshBoolean)
return dynamic_cast<GLGizmoMeshBoolean*>(m_gizmos[MeshBoolean].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == BrimEars)
return dynamic_cast<GLGizmoBrimEars*>(m_gizmos[BrimEars].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else
return false;
}
@@ -543,7 +547,7 @@ bool GLGizmosManager::on_mouse_wheel(const wxMouseEvent &evt)
{
bool processed = false;
if (/*m_current == SlaSupports || m_current == Hollow ||*/ m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) {
if (/*m_current == SlaSupports || m_current == Hollow ||*/ m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == BrimEars) {
float rot = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta();
if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown()
// BBS
@@ -813,21 +817,25 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
int keyCode = evt.GetKeyCode();
bool processed = false;
if (evt.GetEventType() == wxEVT_KEY_UP) {
/*if (m_current == SlaSupports || m_current == Hollow)
if (evt.GetEventType() == wxEVT_KEY_UP)
{
if (/*m_current == SlaSupports || m_current == Hollow ||*/ m_current == BrimEars)
{
bool is_editing = true;
bool is_rectangle_dragging = false;
if (m_current == SlaSupports) {
/*if (m_current == SlaSupports) {
GLGizmoSlaSupports* gizmo = dynamic_cast<GLGizmoSlaSupports*>(get_current());
is_editing = gizmo->is_in_editing_mode();
is_rectangle_dragging = gizmo->is_selection_rectangle_dragging();
}
else {
GLGizmoHollow* gizmo = dynamic_cast<GLGizmoHollow*>(get_current());
} else*/ if (m_current == BrimEars) {
GLGizmoBrimEars* gizmo = dynamic_cast<GLGizmoBrimEars*>(get_current());
is_rectangle_dragging = gizmo->is_selection_rectangle_dragging();
}
/*else {
GLGizmoHollow* gizmo = dynamic_cast<GLGizmoHollow*>(get_current());
is_rectangle_dragging = gizmo->is_selection_rectangle_dragging();
}*/
if (keyCode == WXK_SHIFT)
{
@@ -847,7 +855,7 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
// capture number key
processed = true;
}
}*/
}
if (m_current == Measure || m_current == Assembly) {
if (keyCode == WXK_CONTROL)
gizmo_event(SLAGizmoEventType::CtrlUp, Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.CmdDown());
@@ -865,7 +873,12 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
// m_parent.set_cursor(GLCanvas3D::Cross);
processed = true;
}
else*/ if (m_current == Cut) {
else*/ if ((m_current == BrimEars) && ((keyCode == WXK_SHIFT) || (keyCode == WXK_ALT)))
{
processed = true;
}
else if (m_current == Cut)
{
auto do_move = [this, &processed](double delta_z) {
GLGizmoCut3D* cut = dynamic_cast<GLGizmoCut3D*>(get_current());
cut->shift_cut(delta_z);
@@ -1324,11 +1337,15 @@ bool GLGizmosManager::grabber_contains_mouse() const
bool GLGizmosManager::is_in_editing_mode(bool error_notification) const
{
//if (m_current != SlaSupports || ! dynamic_cast<GLGizmoSlaSupports*>(get_current())->is_in_editing_mode())
/*if (m_current == SlaSupports && dynamic_cast<GLGizmoSlaSupports*>(get_current())->is_in_editing_mode()) {
return true;
} else*/ if (m_current == BrimEars) {
dynamic_cast<GLGizmoBrimEars*>(get_current())->save_model();
return false;
} else {
return false;
}
// BBS: remove SLA editing notification
//return true;
}
@@ -88,6 +88,7 @@ public:
Measure,
Assembly,
Simplify,
BrimEars,
//SlaSupports,
// BBS
//FaceRecognition,
+13 -4
View File
@@ -416,7 +416,7 @@ void ImGuiWrapper::set_language(const std::string &language)
}
else if (lang == "en") {
ranges = ImGui::GetIO().Fonts->GetGlyphRangesEnglish(); // Basic Latin
}
}
else{
ranges = ImGui::GetIO().Fonts->GetGlyphRangesOthers();
}
@@ -560,6 +560,15 @@ ImVec2 ImGuiWrapper::calc_text_size(const wxString &text,
return size;
}
float ImGuiWrapper::find_widest_text(std::vector<wxString> &text_list)
{
float width = .0f;
for(const wxString &text : text_list) {
width = std::max(width, this->calc_text_size(text).x);
}
return width;
}
ImVec2 ImGuiWrapper::calc_button_size(const wxString &text, const ImVec2 &button_size) const
{
const ImVec2 text_size = this->calc_text_size(text);
@@ -1598,9 +1607,9 @@ bool begin_menu(const char *label, bool enabled)
return menu_is_open;
}
void end_menu()
{
ImGui::EndMenu();
void end_menu()
{
ImGui::EndMenu();
}
bool menu_item_with_icon(const char *label, const char *shortcut, ImVec2 icon_size /* = ImVec2(0, 0)*/, ImU32 icon_color /* = 0*/, bool selected /* = false*/, bool enabled /* = true*/, bool* hovered/* = nullptr*/)
+1 -1
View File
@@ -107,7 +107,7 @@ public:
static ImVec2 calc_text_size(const std::string& text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f);
static ImVec2 calc_text_size(const wxString &text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f);
ImVec2 calc_button_size(const wxString &text, const ImVec2 &button_size = ImVec2(0, 0)) const;
float find_widest_text(std::vector<wxString> &text_list);
ImVec2 get_item_spacing() const;
float get_slider_float_height() const;
const LastSliderStatus& get_last_slider_status() const { return m_last_slider_status; }
+10 -1
View File
@@ -269,7 +269,16 @@ void PrintJob::process(Ctl &ctl)
auto model_name = model_info->metadata_items.find(BBL_DESIGNER_MODEL_TITLE_TAG);
if (model_name != model_info->metadata_items.end()) {
try {
params.project_name = model_name->second;
std::string mall_model_name = model_name->second;
std::replace(mall_model_name.begin(), mall_model_name.end(), ' ', '_');
const char* unusable_symbols = "<>[]:/\\|?*\" ";
for (const char* symbol = unusable_symbols; *symbol != '\0'; ++symbol) {
std::replace(mall_model_name.begin(), mall_model_name.end(), *symbol, '_');
}
std::regex pattern("_+");
params.project_name = std::regex_replace(mall_model_name, pattern, "_");
}
catch (...) {}
}
+1 -1
View File
@@ -2721,7 +2721,7 @@ void MainFrame::init_menubar_as_editor()
this, [this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->is_view3D_overhang_shown(); }, this);
append_menu_check_item(
viewMenu, wxID_ANY, _L("Show Selected Outline (Experimental)"), _L("Show outline around selected object in 3D scene"),
viewMenu, wxID_ANY, _L("Show Selected Outline (beta)"), _L("Show outline around selected object in 3D scene"),
[this](wxCommandEvent&) {
wxGetApp().toggle_show_outline();
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
+1 -1
View File
@@ -304,10 +304,10 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init
bbl_render_left_sign(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
render_left_sign(imgui);
render_text(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
m_minimize_b_visible = false;
if (m_multiline && m_lines_count > 3)
render_minimize_button(imgui, win_pos.x, win_pos.y);
render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); // ORCA draw it after minimize button since its position related to minimize button
}
imgui.end();
+8 -7
View File
@@ -2724,7 +2724,7 @@ bool PartPlate::intersects(const BoundingBoxf3& bb) const
return print_volume.intersects(bb);
}
void PartPlate::render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_body, bool force_background_color, HeightLimitMode mode, int hover_id, bool render_cali)
void PartPlate::render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_body, bool force_background_color, HeightLimitMode mode, int hover_id, bool render_cali, bool show_grid)
{
glsafe(::glEnable(GL_DEPTH_TEST));
@@ -2744,7 +2744,8 @@ void PartPlate::render(const Transform3d& view_matrix, const Transform3d& projec
render_exclude_area(force_background_color);
}
render_grid(bottom);
if (show_grid)
render_grid(bottom);
render_height_limit(mode);
@@ -4781,7 +4782,7 @@ void PartPlateList::postprocess_arrange_polygon(arrangement::ArrangePolygon& arr
/*rendering related functions*/
//render
void PartPlateList::render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali)
void PartPlateList::render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali, bool show_grid)
{
const std::lock_guard<std::mutex> local_lock(m_plates_mutex);
std::vector<PartPlate*>::iterator it = m_plate_list.begin();
@@ -4806,15 +4807,15 @@ void PartPlateList::render(const Transform3d& view_matrix, const Transform3d& pr
if (current_index == m_current_plate) {
PartPlate::HeightLimitMode height_mode = (only_current)?PartPlate::HEIGHT_LIMIT_NONE:m_height_limit_mode;
if (plate_hover_index == current_index)
(*it)->render(view_matrix, projection_matrix, bottom, only_body, false, height_mode, plate_hover_action, render_cali);
(*it)->render(view_matrix, projection_matrix, bottom, only_body, false, height_mode, plate_hover_action, render_cali, show_grid);
else
(*it)->render(view_matrix, projection_matrix, bottom, only_body, false, height_mode, -1, render_cali);
(*it)->render(view_matrix, projection_matrix, bottom, only_body, false, height_mode, -1, render_cali, show_grid);
}
else {
if (plate_hover_index == current_index)
(*it)->render(view_matrix, projection_matrix, bottom, only_body, false, PartPlate::HEIGHT_LIMIT_NONE, plate_hover_action, render_cali);
(*it)->render(view_matrix, projection_matrix, bottom, only_body, false, PartPlate::HEIGHT_LIMIT_NONE, plate_hover_action, render_cali, show_grid);
else
(*it)->render(view_matrix, projection_matrix, bottom, only_body, false, PartPlate::HEIGHT_LIMIT_NONE, -1, render_cali);
(*it)->render(view_matrix, projection_matrix, bottom, only_body, false, PartPlate::HEIGHT_LIMIT_NONE, -1, render_cali, show_grid);
}
}
}
+2 -2
View File
@@ -363,7 +363,7 @@ public:
bool contains(const BoundingBoxf3& bb) const;
bool intersects(const BoundingBoxf3& bb) const;
void render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_body = false, bool force_background_color = false, HeightLimitMode mode = HEIGHT_LIMIT_NONE, int hover_id = -1, bool render_cali = false);
void render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_body = false, bool force_background_color = false, HeightLimitMode mode = HEIGHT_LIMIT_NONE, int hover_id = -1, bool render_cali = false, bool show_grid = true);
void set_selected();
void set_unselected();
@@ -781,7 +781,7 @@ public:
/*rendering related functions*/
void on_change_color_mode(bool is_dark) { m_is_dark = is_dark; }
void render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current = false, bool only_body = false, int hover_id = -1, bool render_cali = false);
void render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current = false, bool only_body = false, int hover_id = -1, bool render_cali = false, bool show_grid = true);
void set_render_option(bool bedtype_texture, bool plate_settings);
void set_render_cali(bool value = true) { render_cali_logo = value; }
void register_raycasters_for_picking(GLCanvas3D& canvas)
+139 -82
View File
@@ -1298,8 +1298,10 @@ void Sidebar::update_all_preset_comboboxes()
"curr_bed_type");
if (!str_bed_type.empty()) {
int bed_type_value = atoi(str_bed_type.c_str());
if (bed_type_value == 0)
bed_type_value = 1;
if (bed_type_value <= 0 || bed_type_value >= btCount) {
bed_type_value = preset_bundle.printers.get_edited_preset().get_default_bed_type(&preset_bundle);
}
m_bed_type_list->SelectAndNotify(bed_type_value - 1);
} else {
BedType bed_type = preset_bundle.printers.get_edited_preset().get_default_bed_type(&preset_bundle);
@@ -1307,8 +1309,9 @@ void Sidebar::update_all_preset_comboboxes()
}
}
} else {
// Orca: combobox don't have the btDefault option, so we need to -1
m_bed_type_list->SelectAndNotify(btPEI - 1);
// m_bed_type_list->SelectAndNotify(btPEI - 1);
BedType bed_type = preset_bundle.printers.get_edited_preset().get_default_bed_type(&preset_bundle);
m_bed_type_list->SelectAndNotify((int) bed_type - 1);
m_bed_type_list->Disable();
}
@@ -3356,22 +3359,23 @@ wxColour Plater::get_next_color_for_filament()
static int curr_color_filamenet = 0;
// refs to https://www.ebaomonthly.com/window/photo/lesson/colorList.htm
wxColour colors[FILAMENT_SYSTEM_COLORS_NUM] = {
*wxYELLOW,
* wxRED,
*wxBLUE,
*wxCYAN,
*wxLIGHT_GREY,
*wxWHITE,
*wxBLACK,
wxColour(0,127,255),
wxColour(139,0,255),
wxColour(102,255,0),
wxColour(255,215,0),
wxColour(0,35,100),
wxColour(255,0,255),
wxColour(8,37,103),
wxColour(127,255,212),
wxColour(255,191,0)
// ORCA updated all color palette
wxColour("#00C1AE"),
wxColour("#F4E2C1"),
wxColour("#ED1C24"),
wxColour("#00FF7F"),
wxColour("#F26722"),
wxColour("#FFEB31"),
wxColour("#7841CE"),
wxColour("#115877"),
wxColour("#ED1E79"),
wxColour("#2EBDEF"),
wxColour("#345B2F"),
wxColour("#800080"),
wxColour("#FA8173"),
wxColour("#800000"),
wxColour("#F7B763"),
wxColour("#A4C41E"),
};
return colors[curr_color_filamenet++ % FILAMENT_SYSTEM_COLORS_NUM];
}
@@ -8925,7 +8929,7 @@ int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_
return wxID_YES;
}
LoadType determine_load_type(std::string filename, std::string override_setting = "");
// BBS: FIXME, missing resotre logic
void Plater::load_project(wxString const& filename2,
@@ -8977,8 +8981,16 @@ void Plater::load_project(wxString const& filename2,
auto strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig;
if (originfile == "<silence>") {
strategy = strategy | LoadStrategy::Silence;
} else if (originfile == "<loadall>") {
// Do nothing
} else if (originfile != "-") {
strategy = strategy | LoadStrategy::Restore;
} else {
switch (determine_load_type(filename.ToStdString())) {
case LoadType::OpenProject: break; // Do nothing
case LoadType::LoadGeometry:; strategy = LoadStrategy::LoadModel; break;
default: return; // User cancelled
}
}
bool load_restore = strategy & LoadStrategy::Restore;
@@ -9728,6 +9740,8 @@ auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config
_obj->config.set_key_value("wall_loops", new ConfigOptionInt(1));
_obj->config.set_key_value("only_one_wall_top", new ConfigOptionBool(true));
_obj->config.set_key_value("thick_internal_bridges", new ConfigOptionBool(false));
_obj->config.set_key_value("enable_extra_bridge_layer", new ConfigOptionEnum<EnableExtraBridgeLayer>(eblDisabled));
_obj->config.set_key_value("internal_bridge_density", new ConfigOptionPercent(100));
_obj->config.set_key_value("sparse_infill_density", new ConfigOptionPercent(35));
_obj->config.set_key_value("min_width_top_surface", new ConfigOptionFloatOrPercent(100,true));
_obj->config.set_key_value("bottom_shell_layers", new ConfigOptionInt(2));
@@ -10440,7 +10454,7 @@ private:
wxColour m_def_color = wxColour(255, 255, 255);
RadioSelectorList m_radio_group;
int m_action{1};
bool m_show_again;
bool m_remember_choice{false};
public:
ProjectDropDialog(const std::string &filename);
@@ -10463,7 +10477,7 @@ public:
int get_action() const { return m_action; }
void set_action(int index) { m_action = index; }
wxBoxSizer *create_item_checkbox(wxString title, wxWindow *parent, wxString tooltip, std::string param);
wxBoxSizer *create_remember_checkbox(wxString title, wxWindow* parent, wxString tooltip);
wxBoxSizer *create_item_radiobox(wxString title, wxWindow *parent, int select_id, int groupid);
protected:
@@ -10557,14 +10571,12 @@ ProjectDropDialog::ProjectDropDialog(const std::string &filename)
m_sizer_main->Add(0, 0, 0, wxEXPAND | wxTOP, 10);
wxBoxSizer *m_sizer_bottom = new wxBoxSizer(wxHORIZONTAL);
// hide the "Don't show again" checkbox
//wxBoxSizer *m_sizer_left = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer *m_sizer_left = new wxBoxSizer(wxHORIZONTAL);
//auto dont_show_again = create_item_checkbox(_L("Don't show again"), this, _L("Don't show again"), "show_drop_project_dialog");
//m_sizer_left->Add(dont_show_again, 0, wxALL, 5);
//m_sizer_bottom->Add(m_sizer_left, 0, wxEXPAND, 5);
auto dont_show_again = create_remember_checkbox(_L("Remember my choice."), this, _L("This option can be changed later in preferences, under 'Load Behaviour'."));
m_sizer_left->Add(dont_show_again, 0, wxALL, 5);
m_sizer_bottom->Add(m_sizer_left, 0, wxEXPAND, 5);
m_sizer_bottom->Add(0, 0, 1, wxEXPAND, 5);
wxBoxSizer *m_sizer_right = new wxBoxSizer(wxHORIZONTAL);
@@ -10653,13 +10665,14 @@ wxBoxSizer *ProjectDropDialog ::create_item_radiobox(wxString title, wxWindow *p
return sizer;
}
wxBoxSizer *ProjectDropDialog::create_item_checkbox(wxString title, wxWindow *parent, wxString tooltip, std::string param)
wxBoxSizer *ProjectDropDialog::create_remember_checkbox(wxString title, wxWindow *parent, wxString tooltip)
{
wxBoxSizer *m_sizer_checkbox = new wxBoxSizer(wxHORIZONTAL);
m_sizer_checkbox->Add(0, 0, 0, wxEXPAND | wxLEFT, 5);
auto checkbox = new ::CheckBox(parent);
checkbox->SetValue(m_remember_choice);
checkbox->SetToolTip(tooltip);
m_sizer_checkbox->Add(checkbox, 0, wxALIGN_CENTER, 0);
m_sizer_checkbox->Add(0, 0, 0, wxEXPAND | wxLEFT, 8);
@@ -10667,13 +10680,11 @@ wxBoxSizer *ProjectDropDialog::create_item_checkbox(wxString title, wxWindow *pa
checkbox_title->SetForegroundColour(wxColour(144,144,144));
checkbox_title->SetFont(::Label::Body_13);
checkbox_title->Wrap(-1);
checkbox_title->SetToolTip(tooltip);
m_sizer_checkbox->Add(checkbox_title, 0, wxALIGN_CENTER | wxALL, 3);
m_show_again = wxGetApp().app_config->get(param) == "true" ? true : false;
checkbox->SetValue(m_show_again);
checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, checkbox, param](wxCommandEvent &e) {
m_show_again = m_show_again ? false : true;
checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, checkbox](wxCommandEvent &e) {
m_remember_choice = checkbox->GetValue();
e.Skip();
});
@@ -10739,7 +10750,19 @@ void ProjectDropDialog::on_select_radio(wxMouseEvent &event)
void ProjectDropDialog::on_select_ok(wxMouseEvent &event)
{
wxGetApp().app_config->set_bool("show_drop_project_dialog", m_show_again);
if (m_remember_choice) {
LoadType load_type = static_cast<LoadType>(get_action());
switch (load_type)
{
case LoadType::OpenProject:
wxGetApp().app_config->set(SETTING_PROJECT_LOAD_BEHAVIOUR, OPTION_PROJECT_LOAD_BEHAVIOUR_LOAD_ALL);
break;
case LoadType::LoadGeometry:
wxGetApp().app_config->set(SETTING_PROJECT_LOAD_BEHAVIOUR, OPTION_PROJECT_LOAD_BEHAVIOUR_LOAD_GEOMETRY);
break;
}
}
EndModal(wxID_OK);
}
@@ -10919,6 +10942,35 @@ bool Plater::load_files(const wxArrayString& filenames)
return res;
}
LoadType determine_load_type(std::string filename, std::string override_setting)
{
std::string setting;
if (override_setting != "") {
setting = override_setting;
} else {
setting = wxGetApp().app_config->get(SETTING_PROJECT_LOAD_BEHAVIOUR);
}
if (setting == OPTION_PROJECT_LOAD_BEHAVIOUR_LOAD_GEOMETRY) {
return LoadType::LoadGeometry;
} else if (setting == OPTION_PROJECT_LOAD_BEHAVIOUR_ALWAYS_ASK) {
ProjectDropDialog dlg(filename);
if (dlg.ShowModal() == wxID_OK) {
int choice = dlg.get_action();
LoadType load_type = static_cast<LoadType>(choice);
wxGetApp().app_config->set("import_project_action", std::to_string(choice));
// BBS: jump to plater panel
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
return load_type;
}
return LoadType::Unknown; // Cancel
} else {
return LoadType::OpenProject;
}
}
bool Plater::open_3mf_file(const fs::path &file_path)
{
@@ -10927,31 +10979,16 @@ bool Plater::open_3mf_file(const fs::path &file_path)
return false;
}
LoadType load_type = LoadType::Unknown;
if (!model().objects.empty()) {
bool show_drop_project_dialog = true;
if (show_drop_project_dialog) {
ProjectDropDialog dlg(filename);
if (dlg.ShowModal() == wxID_OK) {
int choice = dlg.get_action();
load_type = static_cast<LoadType>(choice);
wxGetApp().app_config->set("import_project_action", std::to_string(choice));
// BBS: jump to plater panel
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
}
} else
load_type = static_cast<LoadType>(
std::clamp(std::stoi(wxGetApp().app_config->get("import_project_action")), static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig)));
} else
load_type = LoadType::OpenProject;
bool not_empty_plate = !model().objects.empty();
bool load_setting_ask_when_relevant = wxGetApp().app_config->get(SETTING_PROJECT_LOAD_BEHAVIOUR) == OPTION_PROJECT_LOAD_BEHAVIOUR_ASK_WHEN_RELEVANT;
LoadType load_type = determine_load_type(filename, (not_empty_plate && load_setting_ask_when_relevant) ? OPTION_PROJECT_LOAD_BEHAVIOUR_ALWAYS_ASK : "");
if (load_type == LoadType::Unknown) return false;
switch (load_type) {
case LoadType::OpenProject: {
if (wxGetApp().can_load_project())
load_project(from_path(file_path));
load_project(from_path(file_path), "<loadall>");
break;
}
case LoadType::LoadGeometry: {
@@ -12626,38 +12663,58 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
}
}
auto config = get_app_config();
PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups, storage_paths, storage_names, config->get_bool("open_device_tab_post_upload"));
if (dlg.ShowModal() == wxID_OK) {
config->set_bool("open_device_tab_post_upload", dlg.switch_to_device_tab());
upload_job.switch_to_device_tab = dlg.switch_to_device_tab();
upload_job.upload_data.upload_path = dlg.filename();
upload_job.upload_data.post_action = dlg.post_action();
upload_job.upload_data.group = dlg.group();
upload_job.upload_data.storage = dlg.storage();
{
auto preset_bundle = wxGetApp().preset_bundle;
const auto opt = physical_printer_config->option<ConfigOptionEnum<PrintHostType>>("host_type");
const auto host_type = opt != nullptr ? opt->value : htElegooLink;
auto config = get_app_config();
// Show "Is printer clean" dialog for PrusaConnect - Upload and print.
if (std::string(upload_job.printhost->get_name()) == "PrusaConnect" && upload_job.upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
GUI::MessageDialog dlg(nullptr, _L("Is the printer ready? Is the print sheet in place, empty and clean?"), _L("Upload and Print"), wxOK | wxCANCEL);
if (dlg.ShowModal() != wxID_OK)
return;
std::unique_ptr<PrintHostSendDialog> pDlg;
if (host_type == htElegooLink) {
pDlg = std::make_unique<ElegooPrintHostSendDialog>(default_output_file, upload_job.printhost->get_post_upload_actions(), groups,
storage_paths, storage_names,
config->get_bool("open_device_tab_post_upload"));
} else {
pDlg = std::make_unique<PrintHostSendDialog>(default_output_file, upload_job.printhost->get_post_upload_actions(), groups,
storage_paths, storage_names, config->get_bool("open_device_tab_post_upload"));
}
if (use_3mf) {
// Process gcode
const int result = send_gcode(plate_idx, nullptr);
if (result < 0) {
wxString msg = _L("Abnormal print file data. Please slice again");
show_error(this, msg, false);
return;
}
upload_job.upload_data.source_path = p->m_print_job_data._3mf_path;
pDlg->init();
if (pDlg->ShowModal() != wxID_OK) {
return;
}
p->export_gcode(fs::path(), false, std::move(upload_job));
config->set_bool("open_device_tab_post_upload", pDlg->switch_to_device_tab());
// PrintHostUpload upload_data;
upload_job.switch_to_device_tab = pDlg->switch_to_device_tab();
upload_job.upload_data.upload_path = pDlg->filename();
upload_job.upload_data.post_action = pDlg->post_action();
upload_job.upload_data.group = pDlg->group();
upload_job.upload_data.storage = pDlg->storage();
upload_job.upload_data.extended_info = pDlg->extendedInfo();
}
// Show "Is printer clean" dialog for PrusaConnect - Upload and print.
if (std::string(upload_job.printhost->get_name()) == "PrusaConnect" && upload_job.upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
GUI::MessageDialog dlg(nullptr, _L("Is the printer ready? Is the print sheet in place, empty and clean?"), _L("Upload and Print"), wxOK | wxCANCEL);
if (dlg.ShowModal() != wxID_OK)
return;
}
if (use_3mf) {
// Process gcode
const int result = send_gcode(plate_idx, nullptr);
if (result < 0) {
wxString msg = _L("Abnormal print file data. Please slice again");
show_error(this, msg, false);
return;
}
upload_job.upload_data.source_path = p->m_print_job_data._3mf_path;
}
p->export_gcode(fs::path(), false, std::move(upload_job));
}
int Plater::send_gcode(int plate_idx, Export3mfProgressFn proFn)
{
+51 -7
View File
@@ -67,7 +67,7 @@ wxBoxSizer *PreferencesDialog::create_item_title(wxString title, wxWindow *paren
return m_sizer_title;
}
wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxWindow *parent, wxString tooltip, std::string param, std::vector<wxString> vlist)
std::tuple<wxBoxSizer*, ComboBox*> PreferencesDialog::create_item_combobox_base(wxString title, wxWindow* parent, wxString tooltip, std::string param, std::vector<wxString> vlist, unsigned int current_index)
{
wxBoxSizer *m_sizer_combox = new wxBoxSizer(wxHORIZONTAL);
m_sizer_combox->Add(0, 0, 0, wxEXPAND | wxLEFT, 23);
@@ -84,20 +84,58 @@ wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxWindow *pa
combobox->GetDropDown().SetFont(::Label::Body_13);
std::vector<wxString>::iterator iter;
for (iter = vlist.begin(); iter != vlist.end(); iter++) { combobox->Append(*iter); }
for (iter = vlist.begin(); iter != vlist.end(); iter++) {
combobox->Append(*iter);
}
auto use_inch = app_config->get(param);
if (!use_inch.empty()) { combobox->SetSelection(atoi(use_inch.c_str())); }
combobox->SetSelection(current_index);
m_sizer_combox->Add(combobox, 0, wxALIGN_CENTER, 0);
return {m_sizer_combox, combobox};
}
wxBoxSizer* PreferencesDialog::create_item_combobox(wxString title, wxWindow* parent, wxString tooltip, std::string param, std::vector<wxString> vlist)
{
unsigned int current_index = 0;
auto current_setting = app_config->get(param);
if (!current_setting.empty()) {
current_index = atoi(current_setting.c_str());
}
auto [sizer, combobox] = create_item_combobox_base(title, parent, tooltip, param, vlist, current_index);
//// save config
combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param](wxCommandEvent &e) {
combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param](wxCommandEvent& e) {
app_config->set(param, std::to_string(e.GetSelection()));
e.Skip();
});
return m_sizer_combox;
return sizer;
}
wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxWindow *parent, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index)
{
assert(vlist.size() == config_name_index.size());
unsigned int current_index = 0;
auto current_setting = app_config->get(param);
if (!current_setting.empty()) {
auto compare = [current_setting](string possible_setting) { return current_setting == possible_setting; };
auto iterator = find_if(config_name_index.begin(), config_name_index.end(), compare);
current_index = iterator - config_name_index.begin();
}
auto [sizer, combobox] = create_item_combobox_base(title, parent, tooltip, param, vlist, current_index);
//// save config
combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, config_name_index](wxCommandEvent& e) {
app_config->set(param, config_name_index[e.GetSelection()]);
e.Skip();
});
return sizer;
}
wxBoxSizer *PreferencesDialog::create_item_language_combobox(
@@ -1166,6 +1204,11 @@ wxWindow* PreferencesDialog::create_general_page()
// auto item_modelmall = create_item_checkbox(_L("Show online staff-picked models on the home page"), page, _L("Show online staff-picked models on the home page"), 50, "staff_pick_switch");
auto title_project = create_item_title(_L("Project"), page, "");
std::vector<wxString> projectLoadSettingsBehaviourOptions = {_L("Load All"), _L("Ask When Relevant"), _L("Always Ask"), _L("Load Geometry Only")};
std::vector<string> projectLoadSettingsConfigOptions = { OPTION_PROJECT_LOAD_BEHAVIOUR_LOAD_ALL, OPTION_PROJECT_LOAD_BEHAVIOUR_ASK_WHEN_RELEVANT, OPTION_PROJECT_LOAD_BEHAVIOUR_ALWAYS_ASK, OPTION_PROJECT_LOAD_BEHAVIOUR_LOAD_GEOMETRY };
auto item_project_load_behaviour = create_item_combobox(_L("Load Behaviour"), page, _L("Should printer/filament/process settings be loaded when opening a .3mf?"), SETTING_PROJECT_LOAD_BEHAVIOUR, projectLoadSettingsBehaviourOptions, projectLoadSettingsConfigOptions);
auto item_max_recent_count = create_item_input(_L("Maximum recent projects"), "", page, _L("Maximum count of recent projects"), "max_recent_count", [](wxString value) {
long max = 0;
if (value.ToLong(&max))
@@ -1241,6 +1284,7 @@ wxWindow* PreferencesDialog::create_general_page()
// update_modelmall(eee);
// item_region->GetItem(size_t(2))->GetWindow()->Bind(wxEVT_COMBOBOX, update_modelmall);
sizer_page->Add(title_project, 0, wxTOP| wxEXPAND, FromDIP(20));
sizer_page->Add(item_project_load_behaviour, 0, wxTOP, FromDIP(3));
sizer_page->Add(item_max_recent_count, 0, wxTOP, FromDIP(3));
sizer_page->Add(item_save_choise, 0, wxTOP, FromDIP(3));
sizer_page->Add(item_gcodes_warning, 0, wxTOP, FromDIP(3));
+4
View File
@@ -105,6 +105,7 @@ public:
wxBoxSizer *create_item_title(wxString title, wxWindow *parent, wxString tooltip);
wxBoxSizer *create_item_combobox(wxString title, wxWindow *parent, wxString tooltip, std::string param, std::vector<wxString> vlist);
wxBoxSizer *create_item_combobox(wxString title, wxWindow *parent, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index);
wxBoxSizer *create_item_region_combobox(wxString title, wxWindow *parent, wxString tooltip, std::vector<wxString> vlist);
wxBoxSizer *create_item_language_combobox(wxString title, wxWindow *parent, wxString tooltip, int padding_left, std::string param, std::vector<const wxLanguageInfo *> vlist);
wxBoxSizer *create_item_loglevel_combobox(wxString title, wxWindow *parent, wxString tooltip, std::vector<wxString> vlist);
@@ -140,6 +141,9 @@ public:
protected:
void OnSelectTabel(wxCommandEvent &event);
void OnSelectRadio(wxMouseEvent &event);
private:
std::tuple<wxBoxSizer*, ComboBox*> create_item_combobox_base(wxString title, wxWindow* parent, wxString tooltip, std::string param, std::vector<wxString> vlist, unsigned int current_index);
};
wxDECLARE_EVENT(EVT_PREFERENCES_SELECT_TAB, wxCommandEvent);
+339 -1
View File
@@ -47,11 +47,22 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo
, post_upload_action(PrintHostPostUploadAction::None)
, m_paths(storage_paths)
, m_switch_to_device_tab(switch_to_device_tab)
, m_path(path)
, m_post_actions(post_actions)
, m_storage_names(storage_names)
{
#ifdef __APPLE__
txt_filename->OSXDisableAllSmartSubstitutions();
#endif
const AppConfig *app_config = wxGetApp().app_config;
}
void PrintHostSendDialog::init()
{
const auto& path = m_path;
const auto& storage_paths = m_paths;
const auto& post_actions = m_post_actions;
const auto& storage_names = m_storage_names;
const AppConfig* app_config = wxGetApp().app_config;
auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _L("Use forward slashes ( / ) as a directory separator if needed."));
label_dir_hint->Wrap(CONTENT_WIDTH * wxGetApp().em_unit());
@@ -614,4 +625,331 @@ bool PrintHostQueueDialog::load_user_data(int udt, std::vector<int>& vector)
}
return true;
}
ElegooPrintHostSendDialog::ElegooPrintHostSendDialog(const fs::path& path,
PrintHostPostUploadActions post_actions,
const wxArrayString& groups,
const wxArrayString& storage_paths,
const wxArrayString& storage_names,
bool switch_to_device_tab)
: PrintHostSendDialog(path, post_actions, groups, storage_paths, storage_names, switch_to_device_tab)
, m_timeLapse(0)
, m_heatedBedLeveling(0)
, m_BedType(BedType::btPTE)
{}
void ElegooPrintHostSendDialog::init() {
auto preset_bundle = wxGetApp().preset_bundle;
auto model_id = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
if (!(model_id == "Elegoo-CC" || model_id == "Elegoo-C")) {
PrintHostSendDialog::init();
return;
}
const auto& path = m_path;
const auto& storage_paths = m_paths;
const auto& post_actions = m_post_actions;
const auto& storage_names = m_storage_names;
this->SetMinSize(wxSize(500, 300));
const AppConfig* app_config = wxGetApp().app_config;
std::string uploadAndPrint = app_config->get("recent", CONFIG_KEY_UPLOADANDPRINT);
if (!uploadAndPrint.empty())
post_upload_action = static_cast<PrintHostPostUploadAction>(std::stoi(uploadAndPrint));
std::string timeLapse = app_config->get("recent", CONFIG_KEY_TIMELAPSE);
if (!timeLapse.empty())
m_timeLapse = std::stoi(timeLapse);
std::string heatedBedLeveling = app_config->get("recent", CONFIG_KEY_HEATEDBEDLEVELING);
if (!heatedBedLeveling.empty())
m_heatedBedLeveling = std::stoi(heatedBedLeveling);
std::string bedType = app_config->get("recent", CONFIG_KEY_BEDTYPE);
if (!bedType.empty())
m_BedType = static_cast<BedType>(std::stoi(bedType));
auto* label_dir_hint = new wxStaticText(this, wxID_ANY, _L("Use forward slashes ( / ) as a directory separator if needed."));
label_dir_hint->Wrap(CONTENT_WIDTH * wxGetApp().em_unit());
wxSizerFlags flags = wxSizerFlags().Border(wxRIGHT, 16).Expand();
content_sizer->Add(txt_filename, flags);
content_sizer->AddSpacer(4);
content_sizer->Add(label_dir_hint);
content_sizer->AddSpacer(VERT_SPACING);
if (combo_groups != nullptr) {
// Repetier specific: Show a selection of file groups.
auto* label_group = new wxStaticText(this, wxID_ANY, _L("Group"));
content_sizer->Add(label_group);
content_sizer->Add(combo_groups, 0, wxBOTTOM, 2 * VERT_SPACING);
wxString recent_group = from_u8(app_config->get("recent", CONFIG_KEY_GROUP));
if (!recent_group.empty())
combo_groups->SetValue(recent_group);
}
if (combo_storage != nullptr) {
// PrusaLink specific: User needs to choose a storage
auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage") + ":");
content_sizer->Add(label_group);
content_sizer->Add(combo_storage, 0, wxBOTTOM, 2 * VERT_SPACING);
combo_storage->SetValue(storage_names.front());
wxString recent_storage = from_u8(app_config->get("recent", CONFIG_KEY_STORAGE));
if (!recent_storage.empty())
combo_storage->SetValue(recent_storage);
} else if (storage_names.GetCount() == 1) {
// PrusaLink specific: Show which storage has been detected.
auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage") + ": " + storage_names.front());
content_sizer->Add(label_group);
m_preselected_storage = m_paths.front();
}
wxString recent_path = from_u8(app_config->get("recent", CONFIG_KEY_PATH));
if (recent_path.Length() > 0 && recent_path[recent_path.Length() - 1] != '/') {
recent_path += '/';
}
const auto recent_path_len = recent_path.Length();
recent_path += path.filename().wstring();
wxString stem(path.stem().wstring());
const auto stem_len = stem.Length();
txt_filename->SetValue(recent_path);
{
auto checkbox_sizer = new wxBoxSizer(wxHORIZONTAL);
auto checkbox = new ::CheckBox(this, wxID_APPLY);
checkbox->SetValue(m_switch_to_device_tab);
checkbox->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& e) {
m_switch_to_device_tab = e.IsChecked();
e.Skip();
});
checkbox_sizer->Add(checkbox, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
auto checkbox_text = new wxStaticText(this, wxID_ANY, _L("Switch to Device tab after upload."), wxDefaultPosition, wxDefaultSize, 0);
checkbox_sizer->Add(checkbox_text, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
checkbox_text->SetFont(::Label::Body_13);
checkbox_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#323A3D")));
content_sizer->Add(checkbox_sizer);
content_sizer->AddSpacer(VERT_SPACING);
}
warning_text = new wxStaticText(this, wxID_ANY,
_L("The selected bed type does not match the file. Please confirm before starting the print."),
wxDefaultPosition, wxDefaultSize, 0);
uploadandprint_sizer = new wxBoxSizer(wxVERTICAL);
{
auto checkbox_sizer = new wxBoxSizer(wxHORIZONTAL);
auto checkbox = new ::CheckBox(this);
checkbox->SetValue(post_upload_action == PrintHostPostUploadAction::StartPrint);
checkbox->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& e) {
if (e.IsChecked()) {
post_upload_action = PrintHostPostUploadAction::StartPrint;
} else {
post_upload_action = PrintHostPostUploadAction::None;
}
refresh();
e.Skip();
});
checkbox_sizer->Add(checkbox, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
auto checkbox_text = new wxStaticText(this, wxID_ANY, _L("Upload and Print"), wxDefaultPosition, wxDefaultSize, 0);
checkbox_sizer->Add(checkbox_text, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
checkbox_text->SetFont(::Label::Body_13);
checkbox_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#323A3D")));
content_sizer->Add(checkbox_sizer);
content_sizer->AddSpacer(VERT_SPACING);
}
{
auto checkbox_sizer = new wxBoxSizer(wxHORIZONTAL);
auto checkbox = new ::CheckBox(this);
checkbox->SetValue(m_timeLapse == 1);
checkbox->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& e) {
m_timeLapse = e.IsChecked() ? 1 : 0;
e.Skip();
});
checkbox_sizer->AddSpacer(16);
checkbox_sizer->Add(checkbox, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
auto checkbox_text = new wxStaticText(this, wxID_ANY, _L("Time-lapse"), wxDefaultPosition, wxDefaultSize, 0);
checkbox_sizer->Add(checkbox_text, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
checkbox_text->SetFont(::Label::Body_13);
checkbox_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#323A3D")));
uploadandprint_sizer->Add(checkbox_sizer);
uploadandprint_sizer->AddSpacer(VERT_SPACING);
}
{
auto checkbox_sizer = new wxBoxSizer(wxHORIZONTAL);
auto checkbox = new ::CheckBox(this);
checkbox->SetValue(m_heatedBedLeveling == 1);
checkbox->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& e) {
m_heatedBedLeveling = e.IsChecked() ? 1 : 0;
e.Skip();
});
checkbox_sizer->AddSpacer(16);
checkbox_sizer->Add(checkbox, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
auto checkbox_text = new wxStaticText(this, wxID_ANY, _L("Heated Bed Leveling"), wxDefaultPosition, wxDefaultSize, 0);
checkbox_sizer->Add(checkbox_text, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
checkbox_text->SetFont(::Label::Body_13);
checkbox_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#323A3D")));
uploadandprint_sizer->Add(checkbox_sizer);
uploadandprint_sizer->AddSpacer(VERT_SPACING);
}
{
auto radioBoxA = new ::RadioBox(this);
auto radioBoxB = new ::RadioBox(this);
if (m_BedType == BedType::btPC)
radioBoxB->SetValue(true);
else
radioBoxA->SetValue(true);
radioBoxA->Bind(wxEVT_LEFT_DOWN, [this, radioBoxA, radioBoxB](wxMouseEvent& e) {
radioBoxA->SetValue(true);
radioBoxB->SetValue(false);
m_BedType = BedType::btPTE;
refresh();
});
radioBoxB->Bind(wxEVT_LEFT_DOWN, [this, radioBoxA, radioBoxB](wxMouseEvent& e) {
radioBoxA->SetValue(false);
radioBoxB->SetValue(true);
m_BedType = BedType::btPC;
refresh();
});
{
auto radio_sizer = new wxBoxSizer(wxHORIZONTAL);
radio_sizer->AddSpacer(16);
radio_sizer->Add(radioBoxA, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
auto checkbox_text = new wxStaticText(this, wxID_ANY, _L("Textured Build Plate (Side A)"), wxDefaultPosition, wxDefaultSize, 0);
radio_sizer->Add(checkbox_text, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
checkbox_text->SetFont(::Label::Body_13);
checkbox_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#323A3D")));
uploadandprint_sizer->Add(radio_sizer);
uploadandprint_sizer->AddSpacer(VERT_SPACING);
}
{
auto radio_sizer = new wxBoxSizer(wxHORIZONTAL);
radio_sizer->AddSpacer(16);
radio_sizer->Add(radioBoxB, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
auto checkbox_text = new wxStaticText(this, wxID_ANY, _L("Smooth Build Plate (Side B)"), wxDefaultPosition, wxDefaultSize, 0);
radio_sizer->Add(checkbox_text, 0, wxALL | wxALIGN_CENTER, FromDIP(2));
checkbox_text->SetFont(::Label::Body_13);
checkbox_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#323A3D")));
uploadandprint_sizer->Add(radio_sizer);
uploadandprint_sizer->AddSpacer(VERT_SPACING);
}
}
{
auto h_sizer = new wxBoxSizer(wxHORIZONTAL);
warning_text->SetFont(::Label::Body_13);
warning_text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#FF1001")));
// wrapping the text
warning_text->Wrap(350);
h_sizer->AddSpacer(16);
h_sizer->Add(warning_text);
uploadandprint_sizer->Add(h_sizer);
uploadandprint_sizer->AddSpacer(VERT_SPACING);
}
content_sizer->Add(uploadandprint_sizer);
uploadandprint_sizer->Show(post_upload_action == PrintHostPostUploadAction::StartPrint);
warning_text->Show(post_upload_action == PrintHostPostUploadAction::StartPrint && appBedType() != m_BedType);
uploadandprint_sizer->Layout();
if (size_t extension_start = recent_path.find_last_of('.'); extension_start != std::string::npos)
m_valid_suffix = recent_path.substr(extension_start);
// .gcode suffix control
auto validate_path = [this](const wxString& path) -> bool {
if (!path.Lower().EndsWith(m_valid_suffix.Lower())) {
MessageDialog msg_wingow(this,
wxString::Format(_L("Upload filename doesn't end with \"%s\". Do you wish to continue?"),
m_valid_suffix),
wxString(SLIC3R_APP_NAME), wxYES | wxNO);
if (msg_wingow.ShowModal() == wxID_NO)
return false;
}
return true;
};
auto* btn_ok = add_button(wxID_OK, true, _L("Upload"));
btn_ok->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) {
if (validate_path(txt_filename->GetValue())) {
// post_upload_action = PrintHostPostUploadAction::None;
EndDialog(wxID_OK);
}
});
txt_filename->SetFocus();
add_button(wxID_CANCEL, false, _L("Cancel"));
finalize();
#ifdef __linux__
// On Linux with GTK2 when text control lose the focus then selection (colored background) disappears but text color stay white
// and as a result the text is invisible with light mode
// see https://github.com/prusa3d/PrusaSlicer/issues/4532
// Workaround: Unselect text selection explicitly on kill focus
txt_filename->Bind(
wxEVT_KILL_FOCUS,
[this](wxEvent& e) {
e.Skip();
txt_filename->SetInsertionPoint(txt_filename->GetLastPosition());
},
txt_filename->GetId());
#endif /* __linux__ */
Bind(wxEVT_SHOW, [=](const wxShowEvent&) {
// Another similar case where the function only works with EVT_SHOW + CallAfter,
// this time on Mac.
CallAfter([=]() {
txt_filename->SetInsertionPoint(0);
txt_filename->SetSelection(recent_path_len, recent_path_len + stem_len);
});
});
}
void ElegooPrintHostSendDialog::EndModal(int ret)
{
if (ret == wxID_OK) {
AppConfig* app_config = wxGetApp().app_config;
app_config->set("recent", CONFIG_KEY_UPLOADANDPRINT, std::to_string(static_cast<int>(post_upload_action)));
app_config->set("recent", CONFIG_KEY_TIMELAPSE, std::to_string(m_timeLapse));
app_config->set("recent", CONFIG_KEY_HEATEDBEDLEVELING, std::to_string(m_heatedBedLeveling));
app_config->set("recent", CONFIG_KEY_BEDTYPE, std::to_string(static_cast<int>(m_BedType)));
}
PrintHostSendDialog::EndModal(ret);
}
BedType ElegooPrintHostSendDialog::appBedType() const
{
std::string str_bed_type = wxGetApp().app_config->get("curr_bed_type");
int bed_type_value = atoi(str_bed_type.c_str());
return static_cast<BedType>(bed_type_value);
}
void ElegooPrintHostSendDialog::refresh()
{
if (uploadandprint_sizer) {
if (post_upload_action == PrintHostPostUploadAction::StartPrint) {
uploadandprint_sizer->Show(true);
} else {
uploadandprint_sizer->Show(false);
}
}
if (warning_text) {
warning_text->Show(post_upload_action == PrintHostPostUploadAction::StartPrint && appBedType() != m_BedType);
}
this->Layout();
this->Fit();
}
}}
+51 -2
View File
@@ -12,7 +12,7 @@
#include "GUI_Utils.hpp"
#include "MsgDialog.hpp"
#include "../Utils/PrintHost.hpp"
#include "libslic3r/PrintConfig.hpp"
class wxButton;
class wxTextCtrl;
class wxChoice;
@@ -27,6 +27,7 @@ class PrintHostSendDialog : public GUI::MsgDialog
{
public:
PrintHostSendDialog(const boost::filesystem::path &path, PrintHostPostUploadActions post_actions, const wxArrayString& groups, const wxArrayString& storage_paths, const wxArrayString& storage_names, bool switch_to_device_tab);
virtual ~PrintHostSendDialog() {}
boost::filesystem::path filename() const;
PrintHostPostUploadAction post_action() const;
std::string group() const;
@@ -34,7 +35,10 @@ public:
bool switch_to_device_tab() const {return m_switch_to_device_tab;}
virtual void EndModal(int ret) override;
private:
virtual void init();
virtual std::map<std::string, std::string> extendedInfo() const { return {}; }
protected:
wxTextCtrl *txt_filename;
wxComboBox *combo_groups;
wxComboBox* combo_storage;
@@ -43,6 +47,10 @@ private:
wxString m_preselected_storage;
wxArrayString m_paths;
bool m_switch_to_device_tab;
boost::filesystem::path m_path;
PrintHostPostUploadActions m_post_actions;
wxArrayString m_storage_names;
};
@@ -131,6 +139,47 @@ private:
bool load_user_data(int, std::vector<int>&);
};
class ElegooPrintHostSendDialog : public PrintHostSendDialog
{
public:
ElegooPrintHostSendDialog(const boost::filesystem::path& path,
PrintHostPostUploadActions post_actions,
const wxArrayString& groups,
const wxArrayString& storage_paths,
const wxArrayString& storage_names,
bool switch_to_device_tab);
virtual void EndModal(int ret) override;
int timeLapse() const { return m_timeLapse; }
int heatedBedLeveling() const { return m_heatedBedLeveling; }
BedType bedType() const { return m_BedType; }
virtual void init() override;
virtual std::map<std::string, std::string> extendedInfo() const
{
return {{"bedType", std::to_string(static_cast<int>(m_BedType))},
{"timeLapse", std::to_string(m_timeLapse)},
{"heatedBedLeveling", std::to_string(m_heatedBedLeveling)}};
}
private:
BedType appBedType() const;
void refresh();
const char* CONFIG_KEY_UPLOADANDPRINT = "elegoolink_upload_and_print";
const char* CONFIG_KEY_TIMELAPSE = "elegoolink_timelapse";
const char* CONFIG_KEY_HEATEDBEDLEVELING = "elegoolink_heated_bed_leveling";
const char* CONFIG_KEY_BEDTYPE = "elegoolink_bed_type";
private:
wxStaticText* warning_text{nullptr};
wxBoxSizer* uploadandprint_sizer{nullptr};
int m_timeLapse;
int m_heatedBedLeveling;
BedType m_BedType;
};
wxDECLARE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
wxDECLARE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event);
wxDECLARE_EVENT(EVT_PRINTHOST_CANCEL, PrintHostQueueDialog::Event);
+2 -2
View File
@@ -1552,10 +1552,10 @@ wxWindow *SelectMachineDialog::create_ams_checkbox(wxString title, wxWindow *par
sizer_check->Add(check, 0, wxBOTTOM | wxEXPAND | wxTOP, FromDIP(5));
sizer_checkbox->Add(sizer_check, 0, wxEXPAND, FromDIP(5));
sizer_checkbox->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(11));
sizer_checkbox->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(7));
auto text = new wxStaticText(checkbox, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, 0);
text->SetFont(::Label::Body_13);
text->SetFont(::Label::Body_12);
text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#323A3C")));
text->Wrap(-1);
sizer_checkbox->Add(text, 0, wxALIGN_CENTER, 0);
+1
View File
@@ -2077,6 +2077,7 @@ void Selection::copy_to_clipboard()
dst_object->sla_support_points = src_object->sla_support_points;
dst_object->sla_points_status = src_object->sla_points_status;
dst_object->sla_drain_holes = src_object->sla_drain_holes;
dst_object->brim_points = src_object->brim_points;
dst_object->layer_config_ranges = src_object->layer_config_ranges; // #ys_FIXME_experiment
dst_object->layer_height_profile.assign(src_object->layer_height_profile);
dst_object->origin_translation = src_object->origin_translation;
+51 -17
View File
@@ -1628,6 +1628,13 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
}
//Orca: disable purge_in_prime_tower if single_extruder_multi_material is disabled
if (opt_key == "single_extruder_multi_material" && m_config->opt_bool("single_extruder_multi_material") == false){
DynamicPrintConfig new_conf = *m_config;
new_conf.set_key_value("purge_in_prime_tower", new ConfigOptionBool(false));
m_config_manipulation.apply(m_config, &new_conf);
}
if (m_postpone_update_ui) {
// It means that not all values are rolled to the system/last saved values jet.
// And call of the update() can causes a redundant check of the config values,
@@ -2068,8 +2075,10 @@ void TabPrint::build()
optgroup->append_single_option_line("bridge_flow");
optgroup->append_single_option_line("internal_bridge_flow");
optgroup->append_single_option_line("bridge_density");
optgroup->append_single_option_line("internal_bridge_density");
optgroup->append_single_option_line("thick_bridges");
optgroup->append_single_option_line("thick_internal_bridges");
optgroup->append_single_option_line("enable_extra_bridge_layer");
optgroup->append_single_option_line("dont_filter_internal_bridges");
optgroup->append_single_option_line("counterbore_hole_bridging","counterbore-hole-bridging");
@@ -2101,6 +2110,8 @@ void TabPrint::build()
optgroup = page->new_optgroup(L("Infill"), L"param_infill");
optgroup->append_single_option_line("sparse_infill_density");
optgroup->append_single_option_line("sparse_infill_pattern", "fill-patterns#infill types and their properties of sparse");
optgroup->append_single_option_line("lattice_angle_1");
optgroup->append_single_option_line("lattice_angle_2");
optgroup->append_single_option_line("infill_anchor_max");
optgroup->append_single_option_line("infill_anchor");
optgroup->append_single_option_line("internal_solid_infill_pattern");
@@ -2113,6 +2124,7 @@ void TabPrint::build()
optgroup->append_single_option_line("solid_infill_direction");
optgroup->append_single_option_line("rotate_solid_infill_direction");
optgroup->append_single_option_line("bridge_angle");
optgroup->append_single_option_line("internal_bridge_angle"); // ORCA: Internal bridge angle override
optgroup->append_single_option_line("minimum_sparse_infill_area");
optgroup->append_single_option_line("infill_combination");
optgroup->append_single_option_line("infill_combination_max_layer_height");
@@ -2279,6 +2291,7 @@ void TabPrint::build()
optgroup->append_single_option_line("flush_into_support", "reduce-wasting-during-filament-change#wipe-into-support-enabled-by-default");
optgroup = page->new_optgroup(L("Advanced"), L"advanced");
optgroup->append_single_option_line("interlocking_beam");
optgroup->append_single_option_line("interface_shells");
optgroup->append_single_option_line("mmu_segmented_region_max_width");
optgroup->append_single_option_line("mmu_segmented_region_interlocking_depth");
optgroup->append_single_option_line("interlocking_beam_width");
@@ -2312,6 +2325,9 @@ page = add_options_page(L("Others"), "custom-gcode_other"); // ORCA: icon only v
optgroup->append_single_option_line("spiral_mode", "spiral-vase");
optgroup->append_single_option_line("spiral_mode_smooth", "spiral-vase#smooth");
optgroup->append_single_option_line("spiral_mode_max_xy_smoothing", "spiral-vase#max-xy-smoothing");
optgroup->append_single_option_line("spiral_starting_flow_ratio", "spiral-vase#starting-flow-ratio");
optgroup->append_single_option_line("spiral_finishing_flow_ratio", "spiral-vase#finishing-flow-ratio");
optgroup->append_single_option_line("timelapse_type", "Timelapse");
optgroup->append_single_option_line("fuzzy_skin");
@@ -2349,18 +2365,19 @@ page = add_options_page(L("Others"), "custom-gcode_other"); // ORCA: icon only v
option.opt.height = 25;//250;
optgroup->append_single_option_line(option);
page = add_options_page(L("Dependencies"), "custom-gcode_advanced");
optgroup = page->new_optgroup(L("Profile dependencies"));
// Orca: hide the dependencies tab for process for now. The UI is not ready yet.
// page = add_options_page(L("Dependencies"), "custom-gcode_advanced");
// optgroup = page->new_optgroup(L("Profile dependencies"));
create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) {
return compatible_widget_create(parent, m_compatible_printers);
});
// create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) {
// return compatible_widget_create(parent, m_compatible_printers);
// });
option = optgroup->get_option("compatible_printers_condition");
option.opt.full_width = true;
optgroup->append_single_option_line(option);
// option = optgroup->get_option("compatible_printers_condition");
// option.opt.full_width = true;
// optgroup->append_single_option_line(option);
build_preset_description_line(optgroup.get());
// build_preset_description_line(optgroup.get());
}
// Reload current config (aka presets->edited_preset->config) into the UI fields.
@@ -3408,6 +3425,7 @@ void TabFilament::build()
optgroup->append_single_option_line("enable_overhang_bridge_fan", "auto-cooling");
optgroup->append_single_option_line("overhang_fan_threshold", "auto-cooling");
optgroup->append_single_option_line("overhang_fan_speed", "auto-cooling");
optgroup->append_single_option_line("internal_bridge_fan_speed"); // ORCA: Add support for separate internal bridge fan speed control
optgroup->append_single_option_line("support_material_interface_fan_speed");
optgroup = page->new_optgroup(L("Auxiliary part cooling fan"), L"param_cooling_aux_fan");
@@ -3568,7 +3586,7 @@ void TabFilament::toggle_options()
auto cfg = m_preset_bundle->printers.get_edited_preset().config;
if (m_active_page->title() == L("Cooling")) {
bool has_enable_overhang_bridge_fan = m_config->opt_bool("enable_overhang_bridge_fan", 0);
for (auto el : {"overhang_fan_speed", "overhang_fan_threshold"})
for (auto el : {"overhang_fan_speed", "overhang_fan_threshold", "internal_bridge_fan_speed"}) // ORCA: Add support for separate internal bridge fan speed control
toggle_option(el, has_enable_overhang_bridge_fan);
toggle_option("additional_cooling_fan_speed", cfg.opt_bool("auxiliary_fan"));
@@ -3581,13 +3599,29 @@ void TabFilament::toggle_options()
{
bool pa = m_config->opt_bool("enable_pressure_advance", 0);
toggle_option("pressure_advance", pa);
// Orca: Enable the plates that should be visible when multi bed support is enabled or a BBL printer is selected
auto support_multi_bed_types = is_BBL_printer || cfg.opt_bool("support_multi_bed_types");
toggle_line("supertack_plate_temp_initial_layer", support_multi_bed_types );
toggle_line("cool_plate_temp_initial_layer", support_multi_bed_types );
toggle_line("textured_cool_plate_temp_initial_layer", support_multi_bed_types);
toggle_line("eng_plate_temp_initial_layer", support_multi_bed_types);
toggle_line("textured_plate_temp_initial_layer", support_multi_bed_types);
//Orca: Enable the plates that should be visible when multi bed support is enabled or a BBL printer is selected; otherwise, enable only the plate visible for the selected bed type.
DynamicConfig& proj_cfg = m_preset_bundle->project_config;
std::string bed_temp_1st_layer_key = "";
if (proj_cfg.has("curr_bed_type"))
{
bed_temp_1st_layer_key = get_bed_temp_1st_layer_key(proj_cfg.opt_enum<BedType>("curr_bed_type"));
}
const std::vector<std::string> bed_temp_keys = {"supertack_plate_temp_initial_layer", "cool_plate_temp_initial_layer",
"textured_cool_plate_temp_initial_layer", "eng_plate_temp_initial_layer",
"textured_plate_temp_initial_layer", "hot_plate_temp_initial_layer"};
bool support_multi_bed_types = std::find(bed_temp_keys.begin(), bed_temp_keys.end(), bed_temp_1st_layer_key) ==
bed_temp_keys.end() ||
is_BBL_printer || cfg.opt_bool("support_multi_bed_types");
for (const auto& key : bed_temp_keys)
{
toggle_line(key, support_multi_bed_types || bed_temp_1st_layer_key == key);
}
// Orca: adaptive pressure advance and calibration model
// If PA is not enabled, disable adaptive pressure advance and hide the model section
+863
View File
@@ -0,0 +1,863 @@
#include "ElegooLink.hpp"
#include <algorithm>
#include <sstream>
#include <exception>
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/asio.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/nowide/convert.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <curl/curl.h>
#include <wx/progdlg.h>
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/format.hpp"
#include "Http.hpp"
#include "libslic3r/AppConfig.hpp"
#include "Bonjour.hpp"
#include "slic3r/GUI/BonjourDialog.hpp"
namespace fs = boost::filesystem;
namespace pt = boost::property_tree;
#define MAX_UPLOAD_PACKAGE_LENGTH 1048576 //(1024*1024)
namespace Slic3r {
enum ElegooLinkCommand {
//get status
ELEGOO_GET_STATUS = 0,
//get properties
ELEGOO_GET_PROPERTIES = 1,
//start print
ELEGOO_START_PRINT = 128,
};
typedef enum
{
SDCP_PRINT_CTRL_ACK_OK = 0, // OK
SDCP_PRINT_CTRL_ACK_BUSY = 1 , // 设备忙 device is busy
SDCP_PRINT_CTRL_ACK_NOT_FOUND = 2, // 未找到目标文件 file not found
SDCP_PRINT_CTRL_ACK_MD5_FAILED = 3, // MD5校验失败 MD5 check failed
SDCP_PRINT_CTRL_ACK_FILEIO_FAILED = 4, // 文件读取失败 file I/O error
SDCP_PRINT_CTRL_ACK_INVLAID_RESOLUTION = 5, // 文件分辨率不匹配 file resolution is invalid
SDCP_PRINT_CTRL_ACK_UNKNOW_FORMAT = 6, // 无法识别的文件格式 file format is invalid
SDCP_PRINT_CTRL_ACK_UNKNOW_MODEL = 7, // 文件机型不匹配 file model is invalid
} ElegooLinkStartPrintAck;
namespace {
std::string get_host_from_url(const std::string& url_in)
{
std::string url = url_in;
// add http:// if there is no scheme
size_t double_slash = url.find("//");
if (double_slash == std::string::npos)
url = "http://" + url;
std::string out = url;
CURLU* hurl = curl_url();
if (hurl) {
// Parse the input URL.
CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, url.c_str(), 0);
if (rc == CURLUE_OK) {
// Replace the address.
char* host;
rc = curl_url_get(hurl, CURLUPART_HOST, &host, 0);
if (rc == CURLUE_OK) {
char* port;
rc = curl_url_get(hurl, CURLUPART_PORT, &port, 0);
if (rc == CURLUE_OK && port != nullptr) {
out = std::string(host) + ":" + port;
curl_free(port);
} else {
out = host;
curl_free(host);
}
}
else
BOOST_LOG_TRIVIAL(error) << "ElegooLink get_host_from_url: failed to get host form URL " << url;
}
else
BOOST_LOG_TRIVIAL(error) << "ElegooLink get_host_from_url: failed to parse URL " << url;
curl_url_cleanup(hurl);
}
else
BOOST_LOG_TRIVIAL(error) << "ElegooLink get_host_from_url: failed to allocate curl_url";
return out;
}
std::string get_host_from_url_no_port(const std::string& url_in)
{
std::string url = url_in;
// add http:// if there is no scheme
size_t double_slash = url.find("//");
if (double_slash == std::string::npos)
url = "http://" + url;
std::string out = url;
CURLU* hurl = curl_url();
if (hurl) {
// Parse the input URL.
CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, url.c_str(), 0);
if (rc == CURLUE_OK) {
// Replace the address.
char* host;
rc = curl_url_get(hurl, CURLUPART_HOST, &host, 0);
if (rc == CURLUE_OK) {
out = host;
curl_free(host);
}
else
BOOST_LOG_TRIVIAL(error) << "ElegooLink get_host_from_url: failed to get host form URL " << url;
}
else
BOOST_LOG_TRIVIAL(error) << "ElegooLink get_host_from_url: failed to parse URL " << url;
curl_url_cleanup(hurl);
}
else
BOOST_LOG_TRIVIAL(error) << "ElegooLink get_host_from_url: failed to allocate curl_url";
return out;
}
#ifdef WIN32
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
std::string substitute_host(const std::string& orig_addr, std::string sub_addr)
{
// put ipv6 into [] brackets
if (sub_addr.find(':') != std::string::npos && sub_addr.at(0) != '[')
sub_addr = "[" + sub_addr + "]";
#if 0
//URI = scheme ":"["//"[userinfo "@"] host [":" port]] path["?" query]["#" fragment]
std::string final_addr = orig_addr;
// http
size_t double_dash = orig_addr.find("//");
size_t host_start = (double_dash == std::string::npos ? 0 : double_dash + 2);
// userinfo
size_t at = orig_addr.find("@");
host_start = (at != std::string::npos && at > host_start ? at + 1 : host_start);
// end of host, could be port(:), subpath(/) (could be query(?) or fragment(#)?)
// or it will be ']' if address is ipv6 )
size_t potencial_host_end = orig_addr.find_first_of(":/", host_start);
// if there are more ':' it must be ipv6
if (potencial_host_end != std::string::npos && orig_addr[potencial_host_end] == ':' && orig_addr.rfind(':') != potencial_host_end) {
size_t ipv6_end = orig_addr.find(']', host_start);
// DK: Uncomment and replace orig_addr.length() if we want to allow subpath after ipv6 without [] parentheses.
potencial_host_end = (ipv6_end != std::string::npos ? ipv6_end + 1 : orig_addr.length()); //orig_addr.find('/', host_start));
}
size_t host_end = (potencial_host_end != std::string::npos ? potencial_host_end : orig_addr.length());
// now host_start and host_end should mark where to put resolved addr
// check host_start. if its nonsense, lets just use original addr (or resolved addr?)
if (host_start >= orig_addr.length()) {
return final_addr;
}
final_addr.replace(host_start, host_end - host_start, sub_addr);
return final_addr;
#else
// Using the new CURL API for handling URL. https://everything.curl.dev/libcurl/url
// If anything fails, return the input unchanged.
std::string out = orig_addr;
CURLU *hurl = curl_url();
if (hurl) {
// Parse the input URL.
CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, orig_addr.c_str(), 0);
if (rc == CURLUE_OK) {
// Replace the address.
rc = curl_url_set(hurl, CURLUPART_HOST, sub_addr.c_str(), 0);
if (rc == CURLUE_OK) {
// Extract a string fromt the CURL URL handle.
char *url;
rc = curl_url_get(hurl, CURLUPART_URL, &url, 0);
if (rc == CURLUE_OK) {
out = url;
curl_free(url);
} else
BOOST_LOG_TRIVIAL(error) << "ElegooLink substitute_host: failed to extract the URL after substitution";
} else
BOOST_LOG_TRIVIAL(error) << "ElegooLink substitute_host: failed to substitute host " << sub_addr << " in URL " << orig_addr;
} else
BOOST_LOG_TRIVIAL(error) << "ElegooLink substitute_host: failed to parse URL " << orig_addr;
curl_url_cleanup(hurl);
} else
BOOST_LOG_TRIVIAL(error) << "ElegooLink substitute_host: failed to allocate curl_url";
return out;
#endif
}
#endif // WIN32
std::string escape_string(const std::string& unescaped)
{
std::string ret_val;
CURL* curl = curl_easy_init();
if (curl) {
char* decoded = curl_easy_escape(curl, unescaped.c_str(), unescaped.size());
if (decoded) {
ret_val = std::string(decoded);
curl_free(decoded);
}
curl_easy_cleanup(curl);
}
return ret_val;
}
std::string escape_path_by_element(const boost::filesystem::path& path)
{
std::string ret_val = escape_string(path.filename().string());
boost::filesystem::path parent(path.parent_path());
while (!parent.empty() && parent.string() != "/") // "/" check is for case "/file.gcode" was inserted. Then boost takes "/" as parent_path.
{
ret_val = escape_string(parent.filename().string()) + "/" + ret_val;
parent = parent.parent_path();
}
return ret_val;
}
} //namespace
ElegooLink::ElegooLink(DynamicPrintConfig *config):
OctoPrint(config) {
}
const char* ElegooLink::get_name() const { return "ElegooLink"; }
bool ElegooLink::elegoo_test(wxString& msg) const{
const char *name = get_name();
bool res = true;
auto url = make_url("");
// Here we do not have to add custom "Host" header - the url contains host filled by user and libCurl will set the header by itself.
auto http = Http::get(std::move(url));
set_auth(http);
http.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
res = false;
msg = format_error(body, error, status);
})
.on_complete([&, this](std::string body, unsigned) {
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body;
// Check if the response contains "ELEGOO" in any case.
// This is a simple check to see if the response is from an ElegooLink server.
std::regex re("ELEGOO", std::regex::icase);
std::smatch match;
if (std::regex_search(body, match, re)) {
res = true;
} else {
msg = format_error(body, "ElegooLink not detected", 0);
res = false;
}
})
#ifdef WIN32
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
.on_ip_resolve([&](std::string address) {
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
// Remember resolved address to be reused at successive REST API call.
msg = GUI::from_u8(address);
})
#endif // WIN32
.perform_sync();
return res;
}
bool ElegooLink::test(wxString &curl_msg) const{
if(OctoPrint::test(curl_msg)){
return true;
}
curl_msg="";
return elegoo_test(curl_msg);
}
#ifdef WIN32
bool ElegooLink::elegoo_test_with_resolved_ip(wxString& msg) const
{
// Since the request is performed synchronously here,
// it is ok to refer to `msg` from within the closure
const char* name = get_name();
bool res = true;
// Msg contains ip string.
auto url = substitute_host(make_url(""), GUI::into_u8(msg));
msg.Clear();
std::string host = get_host_from_url(m_host);
auto http = Http::get(url); // std::move(url));
// "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable.
// And when creating Http object above, libcurl automatically includes "Host" header from address it got.
// Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back.
// Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse
// proxy is used (issue #9734). Also when allow_ip_resolve = 0, this is not needed, but it should not break anything if it stays.
// https://www.rfc-editor.org/rfc/rfc7230#section-5.4
http.header("Host", host);
set_auth(http);
http.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version at %2% : %3%, HTTP %4%, body: `%5%`") % name % url %
error % status % body;
res = false;
msg = format_error(body, error, status);
})
.on_complete([&, this](std::string body, unsigned) {
// Check if the response contains "ELEGOO" in any case.
// This is a simple check to see if the response is from an ElegooLink server.
std::regex re("ELEGOO", std::regex::icase);
std::smatch match;
if (std::regex_search(body, match, re)) {
res = true;
} else {
msg = format_error(body, "ElegooLink not detected", 0);
res = false;
}
})
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
.perform_sync();
return res;
}
bool ElegooLink::test_with_resolved_ip(wxString& msg) const
{
// Elegoo supports both otcoprint and Elegoo link
if (OctoPrint::test_with_resolved_ip(msg)) {
return true;
}
msg = "";
return elegoo_test_with_resolved_ip(msg);
}
#endif // WIN32
wxString ElegooLink::get_test_ok_msg() const
{
return _L("Connection to ElegooLink works correctly.");
}
wxString ElegooLink::get_test_failed_msg(wxString& msg) const
{
return GUI::format_wxstr("%s: %s", _L("Could not connect to ElegooLink"), msg);
}
#ifdef WIN32
bool ElegooLink::upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn, const boost::asio::ip::address& resolved_addr) const
{
wxString test_msg_or_host_ip = "";
info_fn(L"resolve", boost::nowide::widen(resolved_addr.to_string()));
// If test fails, test_msg_or_host_ip contains the error message.
// Otherwise on Windows it contains the resolved IP address of the host.
// Test_msg already contains resolved ip and will be cleared on start of test().
test_msg_or_host_ip = GUI::from_u8(resolved_addr.to_string());
//Elegoo supports both otcoprint and Elegoo link
if(OctoPrint::test_with_resolved_ip(test_msg_or_host_ip)){
return OctoPrint::upload_inner_with_host(upload_data, prorgess_fn, error_fn, info_fn);
}
test_msg_or_host_ip = GUI::from_u8(resolved_addr.to_string());
if(!elegoo_test_with_resolved_ip(test_msg_or_host_ip)){
error_fn(std::move(test_msg_or_host_ip));
return false;
}
std::string url = substitute_host(make_url("uploadFile/upload"), resolved_addr.to_string());
info_fn(L"resolve", boost::nowide::widen(url));
bool res = loopUpload(url, upload_data, prorgess_fn, error_fn, info_fn);
return res;
}
#endif //WIN32
bool ElegooLink::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
{
// If test fails, test_msg_or_host_ip contains the error message.
// Otherwise on Windows it contains the resolved IP address of the host.
wxString test_msg_or_host_ip;
//Elegoo supports both otcoprint and Elegoo link
if(OctoPrint::test(test_msg_or_host_ip)){
return OctoPrint::upload_inner_with_host(upload_data, prorgess_fn, error_fn, info_fn);
}
test_msg_or_host_ip="";
if(!elegoo_test(test_msg_or_host_ip)){
error_fn(std::move(test_msg_or_host_ip));
return false;
}
std::string url;
#ifdef WIN32
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
if (m_host.find("https://") == 0 || test_msg_or_host_ip.empty() || !GUI::get_app_config()->get_bool("allow_ip_resolve"))
#endif // _WIN32
{
// If https is entered we assume signed ceritificate is being used
// IP resolving will not happen - it could resolve into address not being specified in cert
url = make_url("uploadFile/upload");
}
#ifdef WIN32
else {
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
// Curl uses easy_getinfo to get ip address of last successful transaction.
// If it got the address use it instead of the stored in "host" variable.
// This new address returns in "test_msg_or_host_ip" variable.
// Solves troubles of uploades failing with name address.
// in original address (m_host) replace host for resolved ip
info_fn(L"resolve", test_msg_or_host_ip);
url = substitute_host(make_url("uploadFile/upload"), GUI::into_u8(test_msg_or_host_ip));
BOOST_LOG_TRIVIAL(info) << "Upload address after ip resolve: " << url;
}
#endif // _WIN32
bool res = loopUpload(url, upload_data, prorgess_fn, error_fn, info_fn);
return res;
}
bool ElegooLink::validate_version_text(const boost::optional<std::string> &version_text) const
{
return true;
}
bool ElegooLink::uploadPart(Http &http,std::string md5,std::string uuid,std::string path,
std::string filename,size_t filesize,size_t offset,size_t length,
ProgressFn prorgess_fn,ErrorFn error_fn,InfoFn info_fn)const
{
const char* name = get_name();
bool result = false;
http.form_clear();
http.form_add("Check", "1")
.form_add("S-File-MD5", md5)
.form_add("Offset", std::to_string(offset))
.form_add("Uuid", uuid)
.form_add("TotalSize", std::to_string(filesize))
.form_add_file("File", path, filename, offset, length)
.on_complete([&](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
if (status == 200) {
try {
pt::ptree root;
std::istringstream is(body);
pt::read_json(is, root);
std::string code = root.get<std::string>("code");
if (code == "000000") {
// info_fn(L"complete", wxString());
result = true;
} else {
// get error messages
pt::ptree messages = root.get_child("messages");
std::string error_message = "ErrorCode : " + code + "\n";
for (pt::ptree::value_type& message : messages) {
std::string field = message.second.get<std::string>("field");
std::string msg = message.second.get<std::string>("message");
error_message += field + ":" + msg + "\n";
}
error_fn(wxString::FromUTF8(error_message));
}
} catch (...) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error parsing response: %2%") % name % body;
error_fn(wxString::FromUTF8("Error parsing response"));
}
} else {
error_fn(format_error(body, "upload failed", status));
}
})
.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status %
body;
error_fn(format_error(body, error, status));
})
.on_progress([&](Http::Progress progress, bool& cancel) {
// If upload is finished, do not call progress_fn
// on_complete will be called after some time, so we do not need to call it here
// Because some devices will call on_complete after the upload progress reaches 100%,
// so we need to process it here, based on on_complete
if (progress.ultotal == progress.ulnow) {
// Upload is finished
return;
}
prorgess_fn(std::move(progress), cancel);
if (cancel) {
// Upload was canceled
BOOST_LOG_TRIVIAL(info) << name << ": Upload canceled";
}
})
#ifdef WIN32
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
#endif
.perform_sync();
return result;
}
bool ElegooLink::loopUpload(std::string url, PrintHostUpload upload_data, ProgressFn progress_fn, ErrorFn error_fn, InfoFn info_fn) const
{
const char* name = get_name();
const auto upload_filename = upload_data.upload_path.filename().string();
std::string source_path = upload_data.source_path.string();
// calc file size
std::ifstream file(source_path, std::ios::binary | std::ios::ate);
std::streamsize size = file.tellg();
file.close();
const std::string fileSize = std::to_string(size);
// generate uuid
boost::uuids::random_generator generator;
boost::uuids::uuid uuid = generator();
std::string uuid_string = to_string(uuid);
std::string md5;
bbl_calc_md5(source_path, md5);
auto http = Http::post(url);
#ifdef WIN32
// "Host" header is necessary here. In the workaround above (two mDNS..) we have got IP address from test connection and subsituted
// it into "url" variable. And when creating Http object above, libcurl automatically includes "Host" header from address it got.
// Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back. Not changing the host would work
// on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734). Also
// when allow_ip_resolve = 0, this is not needed, but it should not break anything if it stays.
// https://www.rfc-editor.org/rfc/rfc7230#section-5.4
std::string host = get_host_from_url(m_host);
http.header("Host", host);
http.header("Accept", "application/json, text/plain, */*");
#endif // _WIN32
set_auth(http);
bool res = false;
const int packageCount = (size + MAX_UPLOAD_PACKAGE_LENGTH - 1) / MAX_UPLOAD_PACKAGE_LENGTH;
for (size_t i = 0; i < packageCount; i++) {
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2%/%3%") % name % (i + 1) % packageCount;
const size_t offset = MAX_UPLOAD_PACKAGE_LENGTH * i;
size_t length = MAX_UPLOAD_PACKAGE_LENGTH;
// if it is the last package, the length is the remainder of the file size divided by MAX_UPLOAD_PACKAGE_LENGTH
if ((i == packageCount - 1) && (size % MAX_UPLOAD_PACKAGE_LENGTH > 0)) {
length = size % MAX_UPLOAD_PACKAGE_LENGTH;
}
res = uploadPart(
http, md5, uuid_string, source_path, upload_filename, size, offset, length,
[size, i, progress_fn](Http::Progress progress, bool& cancel) {
Http::Progress p(0, 0, size, i * MAX_UPLOAD_PACKAGE_LENGTH + progress.ulnow, progress.buffer);
progress_fn(p, cancel);
},
error_fn, info_fn);
if (!res) {
break;
}
}
if (res) {
if (upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
// connect to websocket, since the upload is successful, the file will be printed
std::string wsUrl = get_host_from_url_no_port(m_host);
WebSocketClient client;
try {
client.connect(wsUrl, "3030", "/websocket");
} catch (std::exception& e) {
const auto errorString = std::string(e.what());
if (errorString.find("The WebSocket handshake was declined by the remote peer") != std::string::npos) {
// error_fn(
// _L("The number of printer connections has exceeded the limit. Please disconnect other connections, restart the "
// "printer and slicer, and then try again."));
error_fn(_L("The file has been transferred, but some unknown errors occurred. Please check the device page for the file and try to start printing again."));
} else {
error_fn(std::string("\n") + wxString::FromUTF8(e.what()));
}
return false;
}
std::string timeLapse = "0";
std::string heatedBedLeveling = "0";
std::string bedType = "0";
timeLapse = upload_data.extended_info["timeLapse"];
heatedBedLeveling = upload_data.extended_info["heatedBedLeveling"];
bedType = upload_data.extended_info["bedType"];
std::this_thread::sleep_for(std::chrono::seconds(1));
if (checkResult(client, error_fn)) {
// send print command
std::this_thread::sleep_for(std::chrono::seconds(1));
res = print(client, timeLapse, heatedBedLeveling, bedType, upload_filename, error_fn);
}else{
res = false;
}
}
}
return res;
}
bool ElegooLink::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
{
#ifndef WIN32
return upload_inner_with_host(std::move(upload_data), prorgess_fn, error_fn, info_fn);
#else
std::string host = get_host_from_url(m_host);
// decide what to do based on m_host - resolve hostname or upload to ip
std::vector<boost::asio::ip::address> resolved_addr;
boost::system::error_code ec;
boost::asio::ip::address host_ip = boost::asio::ip::make_address(host, ec);
if (!ec) {
resolved_addr.push_back(host_ip);
} else if ( GUI::get_app_config()->get_bool("allow_ip_resolve") && boost::algorithm::ends_with(host, ".local")){
Bonjour("octoprint")
.set_hostname(host)
.set_retries(5) // number of rounds of queries send
.set_timeout(1) // after each timeout, if there is any answer, the resolving will stop
.on_resolve([&ra = resolved_addr](const std::vector<BonjourReply>& replies) {
for (const auto & rpl : replies) {
boost::asio::ip::address ip(rpl.ip);
ra.emplace_back(ip);
BOOST_LOG_TRIVIAL(info) << "Resolved IP address: " << rpl.ip;
}
})
.resolve_sync();
}
if (resolved_addr.empty()) {
// no resolved addresses - try system resolving
BOOST_LOG_TRIVIAL(error) << "ElegooLink failed to resolve hostname " << m_host << " into the IP address. Starting upload with system resolving.";
return upload_inner_with_host(std::move(upload_data), prorgess_fn, error_fn, info_fn);
} else if (resolved_addr.size() == 1) {
// one address resolved - upload there
return upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn, error_fn, info_fn, resolved_addr.front());
} else if (resolved_addr.size() == 2 && resolved_addr[0].is_v4() != resolved_addr[1].is_v4()) {
// there are just 2 addresses and 1 is ip_v4 and other is ip_v6
// try sending to both. (Then if both fail, show both error msg after second try)
wxString error_message;
if (!upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn
, [&msg = error_message, resolved_addr](wxString error) { msg = GUI::format_wxstr("%1%: %2%", resolved_addr.front(), error); }
, info_fn, resolved_addr.front())
&&
!upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn
, [&msg = error_message, resolved_addr](wxString error) { msg += GUI::format_wxstr("\n%1%: %2%", resolved_addr.back(), error); }
, info_fn, resolved_addr.back())
) {
error_fn(error_message);
return false;
}
return true;
} else {
// There are multiple addresses - user needs to choose which to use.
size_t selected_index = resolved_addr.size();
IPListDialog dialog(nullptr, boost::nowide::widen(m_host), resolved_addr, selected_index);
if (dialog.ShowModal() == wxID_OK && selected_index < resolved_addr.size()) {
return upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn, error_fn, info_fn, resolved_addr[selected_index]);
}
}
return false;
#endif // WIN32
}
bool ElegooLink::print(WebSocketClient& client,
std::string timeLapse,
std::string heatedBedLeveling,
std::string bedType,
const std::string filename,
ErrorFn error_fn) const
{
// convert bedType to 0 or 1, 0 is PTE, 1 is PC
if (bedType == std::to_string((int)BedType::btPC)){
bedType = "1";
}else{
bedType = "0";
}
bool res = false;
// create a random UUID generator
boost::uuids::random_generator generator;
// generate a UUID
boost::uuids::uuid uuid = generator();
std::string uuid_string = to_string(uuid);
try {
std::string requestID = uuid_string;
auto now = std::chrono::system_clock::now();
auto duration = now.time_since_epoch();
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
std::string timestamp = std::to_string(milliseconds);
std::string jsonString = R"({
"Id":"",
"Data":{
"Cmd":)"+std::to_string(ElegooLinkCommand::ELEGOO_START_PRINT)+R"(,
"Data":{
"Filename":"/local/)" + filename + R"(",
"StartLayer":0,
"Calibration_switch":)" + heatedBedLeveling + R"(,
"PrintPlatformType":)" + bedType + R"(,
"Tlp_Switch":)" + timeLapse + R"(
},
"RequestID":")"+ uuid_string + R"(",
"MainboardID":"",
"TimeStamp":)" + timestamp + R"(,
"From":1
}
})";
std::cout << "send: " << jsonString << std::endl;
BOOST_LOG_TRIVIAL(info) << "start print, param: " << jsonString;
client.send(jsonString);
// wait 30s
auto start_time = std::chrono::steady_clock::now();
do{
std::string response = client.receive();
std::cout << "Received: " << response << std::endl;
BOOST_LOG_TRIVIAL(info) << "Received: " << response;
//sample response
// {"Id":"979d4C788A4a78bC777A870F1A02867A","Data":{"Cmd":128,"Data":{"Ack":1},"RequestID":"5223de52cc7642ae8d7924f9dd46f6ad","MainboardID":"1c7319d30105041800009c0000000000","TimeStamp":1735032553},"Topic":"sdcp/response/1c7319d30105041800009c0000000000"}
pt::ptree root;
std::istringstream is(response);
pt::read_json(is, root);
auto data = root.get_child_optional("Data");
if(!data){
BOOST_LOG_TRIVIAL(info) << "wait for start print response";
continue;
}
auto cmd = data->get_optional<int>("Cmd");
if(!cmd){
BOOST_LOG_TRIVIAL(info) << "wait for start print response";
continue;
}
if(*cmd == ElegooLinkCommand::ELEGOO_START_PRINT){
auto _ack = data->get_optional<int>("Data.Ack");
if(!_ack){
BOOST_LOG_TRIVIAL(error) << "start print failed, ack not found";
error_fn(_L("Error code not found"));
break;
}
auto ack = static_cast<ElegooLinkStartPrintAck>(*_ack);
if(ack == ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_OK){
res = true;
}else{
res = false;
BOOST_LOG_TRIVIAL(error) << "start print failed: ack: " << ack;
wxString error_message = "";
switch(ack){
case ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_BUSY:
{
error_message =_L("The printer is busy, Please check the device page for the file and try to start printing again.");
break;
}
case ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_NOT_FOUND:
{
error_message =_(L("The file is lost, please check and try again."));
break;
}
case ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_MD5_FAILED:
{
error_message =_(L("The file is corrupted, please check and try again."));
break;
}
case ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_FILEIO_FAILED:
case ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_INVLAID_RESOLUTION:
case ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_UNKNOW_FORMAT:
{
error_message =_(L("Transmission abnormality, please check and try again."));
break;
}
case ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_UNKNOW_MODEL:
{
error_message =_(L("The file does not match the printer, please check and try again."));
break;
}
default:
{
error_message =_L("Unknown error");
break;
}
}
error_message += " " + wxString::Format(_L("Error code: %d"),ack);
error_fn(error_message);
}
break;
}
} while (std::chrono::steady_clock::now() - start_time < std::chrono::seconds(30));
if (std::chrono::steady_clock::now() - start_time >= std::chrono::seconds(30)) {
res = false;
error_fn(_L("Start print timeout"));
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
BOOST_LOG_TRIVIAL(error) << "start print error: " << e.what();
error_fn(_L("Start print failed") +"\n" +GUI::from_u8(e.what()));
res=false;
}
return res;
}
bool ElegooLink::checkResult(WebSocketClient& client, ErrorFn error_fn) const
{
bool res = true;
// create a random UUID generator
boost::uuids::random_generator generator;
// generate a UUID
boost::uuids::uuid uuid = generator();
std::string uuid_string = to_string(uuid);
try {
std::string requestID = uuid_string;
auto now = std::chrono::system_clock::now();
auto duration = now.time_since_epoch();
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
std::string timestamp = std::to_string(milliseconds);
std::string jsonString = R"({
"Id":"",
"Data":{
"Cmd":)" +
std::to_string(ElegooLinkCommand::ELEGOO_GET_STATUS) + R"(,
"Data":{},
"RequestID":")" +
uuid_string + R"(",
"MainboardID":"",
"TimeStamp":)" +
timestamp + R"(,
"From":1
}
})";
std::cout << "send: " << jsonString << std::endl;
BOOST_LOG_TRIVIAL(info) << "start print, param: " << jsonString;
bool needWrite = true;
// wait 60s
auto start_time = std::chrono::steady_clock::now();
do {
if (needWrite) {
client.send(jsonString);
needWrite = false;
}
std::string response = client.receive();
std::cout << "Received: " << response << std::endl;
BOOST_LOG_TRIVIAL(info) << "Received: " << response;
pt::ptree root;
std::istringstream is(response);
pt::read_json(is, root);
auto status = root.get_child_optional("Status");
if (status) {
auto currentStatus = status->get_child_optional("CurrentStatus");
if (currentStatus) {
std::vector<int> status;
for (auto& item : *currentStatus) {
status.push_back(item.second.get_value<int>());
}
if (std::find(status.begin(), status.end(), 8) != status.end()) {
// 8 is check file status, need to wait
needWrite = true;
// sleep 1s
std::this_thread::sleep_for(std::chrono::seconds(1));
} else {
break;
}
} else {
break;
}
}
} while (std::chrono::steady_clock::now() - start_time < std::chrono::seconds(60));
if (std::chrono::steady_clock::now() - start_time >= std::chrono::seconds(60)) {
res = false;
error_fn(_L("Start print timeout"));
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
BOOST_LOG_TRIVIAL(error) << "start print error: " << e.what();
error_fn(_L("Start print failed") + "\n" + GUI::from_u8(e.what()));
res = false;
}
return res;
}
}
+72
View File
@@ -0,0 +1,72 @@
#ifndef slic3r_ElegooLink_hpp_
#define slic3r_ElegooLink_hpp_
#include <string>
#include <wx/string.h>
#include <boost/optional.hpp>
#include <boost/asio/ip/address.hpp>
#include "PrintHost.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "OctoPrint.hpp"
#include "WebSocketClient.hpp"
namespace Slic3r {
class DynamicPrintConfig;
class Http;
class ElegooLink : public OctoPrint
{
public:
ElegooLink(DynamicPrintConfig *config);
~ElegooLink() override = default;
const char* get_name() const override;
virtual bool test(wxString &curl_msg) const override;
wxString get_test_ok_msg() const override;
wxString get_test_failed_msg(wxString& msg) const override;
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
bool has_auto_discovery() const override { return false; }
bool can_test() const override { return true; }
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
protected:
#ifdef WIN32
virtual bool upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn, const boost::asio::ip::address& resolved_addr) const;
#endif
virtual bool validate_version_text(const boost::optional<std::string> &version_text) const;
virtual bool upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const;
#ifdef WIN32
virtual bool test_with_resolved_ip(wxString& curl_msg) const override;
bool elegoo_test_with_resolved_ip(wxString& curl_msg) const;
#endif
private:
bool elegoo_test(wxString& curl_msg) const;
bool print(WebSocketClient& client,
std::string timeLapse,
std::string heatedBedLeveling,
std::string bedType,
const std::string filename, ErrorFn error_fn) const;
bool checkResult(WebSocketClient& client,
ErrorFn error_fn) const;
bool loopUpload(std::string url, PrintHostUpload upload_data,
ProgressFn prorgess_fn,
ErrorFn error_fn,
InfoFn info_fn) const;
bool uploadPart(Http &http,
std::string md5,
std::string uuid,
std::string path,
std::string filename,
size_t filesize,
size_t offset,
size_t length,
ProgressFn prorgess_fn,
ErrorFn error_fn,
InfoFn info_fn) const;
};
}
#endif
+20
View File
@@ -192,6 +192,11 @@ Http::priv::priv(const std::string &url)
::curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
// https://everything.curl.dev/http/post/expect100.html
// remove the Expect: header, it will add a second delay to each request,
// if the file is uploaded in packets, it will cause the upload time to be longer
headerlist = curl_slist_append(headerlist, "Expect:");
}
Http::priv::~priv()
@@ -599,6 +604,21 @@ Http& Http::ca_file(const std::string &name)
return *this;
}
Http& Http::form_clear() {
if (p) {
if (p->form) {
::curl_formfree(p->form);
p->form = nullptr;
p->form_end = nullptr;
}
for (auto &f : p->form_files) {
f.ifs.close();
}
p->form_files.clear();
}
return *this;
}
Http& Http::form_add(const std::string &name, const std::string &contents)
{
+1
View File
@@ -119,6 +119,7 @@ public:
// See also ca_file_supported().
Http& ca_file(const std::string &filename);
Http& form_clear();
// Add a HTTP multipart form field
Http& form_add(const std::string &name, const std::string &contents);
// Add a HTTP multipart form file data contents, `name` is the name of the part
+1 -2
View File
@@ -49,9 +49,8 @@ protected:
virtual void set_auth(Http &http) const;
std::string make_url(const std::string &path) const;
private:
#ifdef WIN32
bool test_with_resolved_ip(wxString& curl_msg) const;
virtual bool test_with_resolved_ip(wxString& curl_msg) const;
#endif
};
+12 -1
View File
@@ -26,6 +26,7 @@
#include "Obico.hpp"
#include "Flashforge.hpp"
#include "SimplyPrint.hpp"
#include "ElegooLink.hpp"
namespace fs = boost::filesystem;
using boost::optional;
@@ -65,6 +66,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
case htObico: return new Obico(config);
case htFlashforge: return new Flashforge(config);
case htSimplyPrint: return new SimplyPrint(config);
case htElegooLink: return new ElegooLink(config);
default: return nullptr;
}
} else {
@@ -78,7 +80,16 @@ wxString PrintHost::format_error(const std::string &body, const std::string &err
auto wxbody = wxString::FromUTF8(body.data());
return wxString::Format("HTTP %u: %s", status, wxbody);
} else {
return wxString::FromUTF8(error.data());
if (error.find("curl:Timeout was reached") != std::string::npos) {
return _L("Connection timed out. Please check if the printer and computer network are functioning properly, and confirm that they are on the same network.");
}else if(error.find("curl:Couldn't resolve host name")!= std::string::npos){
return _L("The Hostname/IP/URL could not be parsed, please check it and try again.");
} else if (error.find("Connection was reset") != std::string::npos){
return _L("File/data transfer interrupted. Please check the printer and network, then try it again.");
}
else {
return wxString::FromUTF8(error.data());
}
}
}
+4 -1
View File
@@ -11,7 +11,7 @@
#include <libslic3r/enum_bitmask.hpp>
#include "Http.hpp"
#include <map>
class wxArrayString;
namespace Slic3r {
@@ -37,6 +37,9 @@ struct PrintHostUpload
std::string storage;
PrintHostPostUploadAction post_action { PrintHostPostUploadAction::None };
// Some extended parameters for different upload methods.
std::map<std::string, std::string> extended_info;
};
class PrintHost
+101
View File
@@ -0,0 +1,101 @@
#ifndef _WEB_SOCKET_CLIENT_HPP_
#define _WEB_SOCKET_CLIENT_HPP_
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <iostream>
#include <string>
#include <chrono>
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
class WebSocketClient {
public:
//服务器是ws://echo.websocket.org:80/websocket
WebSocketClient():
resolver_(ioc_), ws_(ioc_),is_connect(false) {
}
~WebSocketClient() {
if(!is_connect){
return;
}
try {
// Close the WebSocket connection
ws_.close(websocket::close_code::normal);
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
void connect(const std::string& host, const std::string& port, const std::string& path="/"){
if(is_connect){
return;
}
// Look up the domain name
auto const results = resolver_.resolve(host, port);
// Make the connection on the IP address we get from a lookup
auto ep = net::connect(ws_.next_layer(), results);
std::string _host = host;
//if _host last char is '/', remove it
if(_host.size()>0&&_host[host.size()-1] == '/'){
_host[host.size()-1] = '\0';
}
// _host += ':' + std::to_string(ep.port());
// Set a decorator to change the User-Agent of the handshake
ws_.set_option(websocket::stream_base::decorator(
[](websocket::request_type& req)
{
req.set(http::field::user_agent,"ElegooSlicer");
}));
// Perform the WebSocket handshake
ws_.handshake(_host, path);
is_connect = true;
}
void send(const std::string& message){
// Send a message
ws_.write(net::buffer(message));
}
std::string receive(int timeout = 0){
// This buffer will hold the incoming message
beast::flat_buffer buffer;
// Read a message into our buffer
ws_.read(buffer);
// Return the message as a string
return beast::buffers_to_string(buffer.data());
}
private:
net::io_context ioc_;
tcp::resolver resolver_;
websocket::stream<tcp::socket> ws_;
bool is_connect;
};
// int main() {
// try {
// WebSocketClient client("echo.websocket.org", "80");
// client.send("Hello, world!");
// std::string response = client.receive();
// std::cout << "Received: " << response << std::endl;
// } catch (const std::exception& e) {
// std::cerr << "Error: " << e.what() << std::endl;
// return EXIT_FAILURE;
// }
// return EXIT_SUCCESS;
// }
#endif // _WEB_SOCKET_CLIENT_HPP