mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 19:01:02 +00:00
Merge branch 'main' into dev/support-paint-vertical
This commit is contained in:
@@ -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];
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 ¢er_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
|
||||
|
||||
@@ -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
|
||||
@@ -43,6 +43,7 @@ set(lisbslic3r_sources
|
||||
FaceDetector.hpp
|
||||
Brim.cpp
|
||||
Brim.hpp
|
||||
BrimEarsPoint.hpp
|
||||
BuildVolume.cpp
|
||||
BuildVolume.hpp
|
||||
Circle.cpp
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) };
|
||||
|
||||
@@ -3002,6 +3002,23 @@ Polylines FillGrid::fill_surface(const Surface *surface, const FillParams ¶m
|
||||
return polylines_out;
|
||||
}
|
||||
|
||||
Polylines Fill2DLattice::fill_surface(const Surface *surface, const FillParams ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
Polylines polylines_out;
|
||||
|
||||
@@ -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 ¶ms) 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:
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
@@ -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";
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, ¤t_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, ¤t_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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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()) {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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>(®ion)), 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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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*/)
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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_
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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*/)
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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 (...) {}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user