Merge branch 'master' of https://github.com/Prusa-Development/PrusaSlicerPrivate into et_transformations

This commit is contained in:
enricoturri1966 2023-02-20 11:00:16 +01:00
commit 8de9a1669c
61 changed files with 31124 additions and 29811 deletions

View file

@ -237,14 +237,14 @@ documentation_link = https://help.prusa3d.com/article/prusaslicer-printables-com
weight = 3
[hint:Cut tool]
text = Cut tool\nDid you know that you can cut a model at any angle and even create aligning pins with the updated Cut tool? Learn more in the documentation.
text = Cut tool\nDid you know that you can cut a model at any angle and even create aligning pins with the updated <a>Cut tool</a>? Learn more in the documentation.
documentation_link = https://help.prusa3d.com/article/cut-tool_1779
hypertext_type = gizmo
hypertext_gizmo_item = cut
weight = 3
[hint:Measurement tool]
text = Measurement tool\nDid you know that you can measure the distances between points, edges and planes, the radius of a hole or the angle between edges or planes? Learn more in the documentation.
text = Measurement tool\nDid you know that you can <a>measure</a> the distances between points, edges and planes, the radius of a hole or the angle between edges or planes? Learn more in the documentation.
documentation_link = https://help.prusa3d.com/article/measurement-tool_399451
hypertext_type = gizmo
hypertext_gizmo_item = measure

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -108,6 +108,10 @@ inline cInt Round(double val)
return static_cast<cInt>((val < 0) ? (val - 0.5) : (val + 0.5));
}
// Overriding the Eigen operators because we don't want to compare Z coordinate if IntPoint is 3 dimensional.
inline bool operator==(const IntPoint &l, const IntPoint &r) { return l.x() == r.x() && l.y() == r.y(); }
inline bool operator!=(const IntPoint &l, const IntPoint &r) { return l.x() != r.x() || l.y() != r.y(); }
//------------------------------------------------------------------------------
// PolyTree methods ...
//------------------------------------------------------------------------------
@ -178,19 +182,25 @@ double Area(const Path &poly)
}
//------------------------------------------------------------------------------
double Area(const OutRec &outRec)
double Area(const OutPt *op)
{
OutPt *op = outRec.Pts;
const OutPt *startOp = op;
if (!op) return 0;
double a = 0;
do {
a += (double)(op->Prev->Pt.x() + op->Pt.x()) * (double)(op->Prev->Pt.y() - op->Pt.y());
op = op->Next;
} while (op != outRec.Pts);
} while (op != startOp);
return a * 0.5;
}
//------------------------------------------------------------------------------
double Area(const OutRec &outRec)
{
return Area(outRec.Pts);
}
//------------------------------------------------------------------------------
bool PointIsVertex(const IntPoint &Pt, OutPt *pp)
{
OutPt *pp2 = pp;
@ -524,27 +534,32 @@ bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2)
p = btmPt2->Next;
while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next;
double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt));
return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n);
if (std::max(dx1p, dx1n) == std::max(dx2p, dx2n) &&
std::min(dx1p, dx1n) == std::min(dx2p, dx2n))
return Area(btmPt1) > 0; //if otherwise identical use orientation
else
return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n);
}
//------------------------------------------------------------------------------
// Called by GetLowermostRec()
OutPt* GetBottomPt(OutPt *pp)
{
OutPt* dups = 0;
OutPt* dups = nullptr;
OutPt* p = pp->Next;
while (p != pp)
{
if (p->Pt.y() > pp->Pt.y())
{
pp = p;
dups = 0;
dups = nullptr;
}
else if (p->Pt.y() == pp->Pt.y() && p->Pt.x() <= pp->Pt.x())
{
if (p->Pt.x() < pp->Pt.x())
{
dups = 0;
dups = nullptr;
pp = p;
} else
{
@ -565,6 +580,7 @@ OutPt* GetBottomPt(OutPt *pp)
}
return pp;
}
//------------------------------------------------------------------------------
bool Pt2IsBetweenPt1AndPt3(const IntPoint &pt1,

View file

@ -38,16 +38,16 @@ class AnyPtr {
}
public:
template<class TT = T, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
template<class TT = T, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>>
AnyPtr(TT *p = nullptr) : ptr{p}
{}
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>>
AnyPtr(std::unique_ptr<TT> p) : ptr{std::unique_ptr<T>(std::move(p))}
{}
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>>
AnyPtr(std::shared_ptr<TT> p) : ptr{std::shared_ptr<T>(std::move(p))}
{}
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>>
AnyPtr(std::weak_ptr<TT> p) : ptr{std::weak_ptr<T>(std::move(p))}
{}
@ -59,16 +59,16 @@ public:
AnyPtr &operator=(AnyPtr &&other) noexcept { ptr = std::move(other.ptr); return *this; }
AnyPtr &operator=(const AnyPtr &other) = delete;
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>>
AnyPtr &operator=(TT *p) { ptr = p; return *this; }
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>>
AnyPtr &operator=(std::unique_ptr<TT> p) { ptr = std::move(p); return *this; }
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>>
AnyPtr &operator=(std::shared_ptr<TT> p) { ptr = p; return *this; }
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>>
AnyPtr &operator=(std::weak_ptr<TT> p) { ptr = std::move(p); return *this; }
const T &operator*() const { return *get_ptr(*this); }

View file

@ -26,7 +26,6 @@ MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartT &csgpart)
MeshBoolean::cgal::CGALMeshPtr ret;
indexed_triangle_set m = *its;
auto tr = get_transform(csgpart);
its_transform(m, get_transform(csgpart), true);
try {

View file

@ -1194,7 +1194,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() }));
std::vector<unsigned char> is_extruder_used(print.config().nozzle_diameter.size(), 0);
for (unsigned int extruder_id : print.extruders())
for (unsigned int extruder_id : tool_ordering.all_extruders())
is_extruder_used[extruder_id] = true;
m_placeholder_parser.set("is_extruder_used", new ConfigOptionBools(is_extruder_used));
}
@ -2793,8 +2793,14 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
acceleration = m_config.first_layer_acceleration_over_raft.value;
} else if (m_config.bridge_acceleration.value > 0 && path.role().is_bridge()) {
acceleration = m_config.bridge_acceleration.value;
} else if (m_config.top_solid_infill_acceleration > 0 && path.role() == ExtrusionRole::TopSolidInfill) {
acceleration = m_config.top_solid_infill_acceleration.value;
} else if (m_config.solid_infill_acceleration > 0 && path.role().is_solid_infill()) {
acceleration = m_config.solid_infill_acceleration.value;
} else if (m_config.infill_acceleration.value > 0 && path.role().is_infill()) {
acceleration = m_config.infill_acceleration.value;
} else if (m_config.external_perimeter_acceleration > 0 && path.role().is_external_perimeter()) {
acceleration = m_config.external_perimeter_acceleration.value;
} else if (m_config.perimeter_acceleration.value > 0 && path.role().is_perimeter()) {
acceleration = m_config.perimeter_acceleration.value;
} else {

View file

@ -3378,9 +3378,9 @@ void GCodeProcessor::process_T(const std::string_view command)
extra_time += m_kissslicer_toolchange_time_correction;
simulate_st_synchronize(extra_time);
// specific to single extruder multi material, set the extruder temperature
// if not done yet
if (m_single_extruder_multi_material && m_extruder_temps[m_extruder_id] == 0.0f)
// specific to single extruder multi material, set the new extruder temperature
// to match the old one
if (m_single_extruder_multi_material)
m_extruder_temps[m_extruder_id] = m_extruder_temps[old_extruder_id];
m_result.extruders_count = std::max<size_t>(m_result.extruders_count, m_extruder_id + 1);

View file

@ -53,22 +53,7 @@ void Layer::make_slices()
this->lslices = slices;
}
// prepare lslices ordered by print order
this->lslice_indices_sorted_by_print_order.clear();
this->lslice_indices_sorted_by_print_order.reserve(lslices.size());
// prepare ordering points
Points ordering_points;
ordering_points.reserve( this->lslices.size());
for (const ExPolygon &ex : this->lslices)
ordering_points.push_back(ex.contour.first_point());
// sort slices
std::vector<Points::size_type> order = chain_points(ordering_points);
// populate slices vector
for (size_t i : order) {
this->lslice_indices_sorted_by_print_order.emplace_back(i);
}
this->lslice_indices_sorted_by_print_order = chain_expolygons(this->lslices);
}
// used by Layer::build_up_down_graph()
@ -105,7 +90,7 @@ static void connect_layer_slices(
const coord_t offset_below,
const coord_t offset_above
#ifndef NDEBUG
, const coord_t offset_end
, const coord_t offset_end
#endif // NDEBUG
)
{
@ -127,9 +112,7 @@ static void connect_layer_slices(
{
#ifndef NDEBUG
auto assert_intersection_valid = [this](int i, int j) {
assert(i != j);
if (i > j)
std::swap(i, j);
assert(i < j);
assert(i >= m_offset_below);
assert(i < m_offset_above);
assert(j >= m_offset_above);
@ -140,35 +123,47 @@ static void connect_layer_slices(
if (polynode.Contour.size() >= 3) {
// If there is an intersection point, it should indicate which contours (one from layer below, the other from layer above) intersect.
// Otherwise the contour is fully inside another contour.
int32_t i = 0, j = 0;
int32_t i = -1, j = -1;
for (int icontour = 0; icontour <= polynode.ChildCount(); ++ icontour) {
const bool first = icontour == 0;
const ClipperLib_Z::Path &contour = first ? polynode.Contour : polynode.Childs[icontour - 1]->Contour;
const ClipperLib_Z::Path &contour = icontour == 0 ? polynode.Contour : polynode.Childs[icontour - 1]->Contour;
if (contour.size() >= 3) {
if (first) {
i = contour.front().z();
j = i;
if (i < 0) {
std::tie(i, j) = m_intersections[-i - 1];
assert(assert_intersection_valid(i, j));
goto end;
}
}
for (const ClipperLib_Z::IntPoint& pt : contour) {
for (const ClipperLib_Z::IntPoint &pt : contour) {
j = pt.z();
if (j < 0) {
std::tie(i, j) = m_intersections[-j - 1];
const auto &intersection = m_intersections[-j - 1];
assert(intersection.first <= intersection.second);
if (intersection.second < m_offset_above) {
// Ignore intersection of polygons on the 1st layer.
assert(intersection.first >= m_offset_below);
j = i;
} else if (intersection.first >= m_offset_above) {
// Ignore intersection of polygons on the 2nd layer
assert(intersection.second < m_offset_end);
j = i;
} else {
std::tie(i, j) = m_intersections[-j - 1];
assert(assert_intersection_valid(i, j));
goto end;
}
} else if (i == -1) {
// First source contour of this expolygon was found.
i = j;
} else if (i != j) {
// Second source contour of this expolygon was found.
if (i > j)
std::swap(i, j);
assert(assert_intersection_valid(i, j));
goto end;
}
else if (i != j)
goto end;
}
}
}
end:
bool found = false;
if (i == j) {
if (i == -1) {
// This should not happen. It may only happen if the source contours had just self intersections or intersections with contours at the same layer.
assert(false);
} else if (i == j) {
// The contour is completely inside another contour.
Point pt(polynode.Contour.front().x(), polynode.Contour.front().y());
if (i < m_offset_above) {
@ -202,8 +197,6 @@ static void connect_layer_slices(
}
} else {
assert(assert_intersection_valid(i, j));
if (i > j)
std::swap(i, j);
i -= m_offset_below;
j -= m_offset_above;
assert(i >= 0 && i < m_below.lslices_ex.size());

View file

@ -318,7 +318,7 @@ public:
Layer *upper_layer;
Layer *lower_layer;
bool slicing_errors;
// bool slicing_errors;
coordf_t slice_z; // Z used for slicing in unscaled coordinates
coordf_t print_z; // Z used for printing in unscaled coordinates
coordf_t height; // layer height in unscaled coordinates
@ -387,7 +387,8 @@ protected:
friend std::string fix_slicing_errors(LayerPtrs&, const std::function<void()>&);
Layer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) :
upper_layer(nullptr), lower_layer(nullptr), slicing_errors(false),
upper_layer(nullptr), lower_layer(nullptr),
//slicing_errors(false),
slice_z(slice_z), print_z(print_z), height(height),
m_id(id), m_object(object) {}
virtual ~Layer();

View file

@ -443,6 +443,7 @@ static std::vector<std::string> s_Preset_print_options {
"enable_dynamic_overhang_speeds", "dynamic_overhang_speeds", "overhang_overlap_levels",
"top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed",
"bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "first_layer_speed_over_raft", "perimeter_acceleration", "infill_acceleration",
"external_perimeter_acceleration", "top_solid_infill_acceleration", "solid_infill_acceleration",
"bridge_acceleration", "first_layer_acceleration", "first_layer_acceleration_over_raft", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield",
"min_skirt_length", "brim_width", "brim_separation", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
@ -457,7 +458,7 @@ static std::vector<std::string> s_Preset_print_options {
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio", "clip_multipart_objects",
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio",
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
"wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits",

View file

@ -74,6 +74,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"duplicate_distance",
"end_gcode",
"end_filament_gcode",
"external_perimeter_acceleration",
"extrusion_axis",
"extruder_clearance_height",
"extruder_clearance_radius",
@ -125,10 +126,12 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"retract_speed",
"single_extruder_multi_material_priming",
"slowdown_below_layer_time",
"solid_infill_acceleration",
"standby_temperature_delta",
"start_gcode",
"start_filament_gcode",
"toolchange_gcode",
"top_solid_infill_acceleration",
"thumbnails",
"thumbnails_format",
"use_firmware_retraction",

View file

@ -598,14 +598,6 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.f));
def = this->add("clip_multipart_objects", coBool);
def->label = L("Clip multi-part objects");
def->tooltip = L("When printing multi-material objects, this settings will make Slic3r "
"to clip the overlapping object parts one by the other "
"(2nd part will be clipped by the 1st, 3rd part will be clipped by the 1st and 2nd etc).");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(true));
def = this->add("colorprint_heights", coFloats);
def->label = L("Colorprint height");
def->tooltip = L("Heights at which a filament change is to occur.");
@ -1398,12 +1390,31 @@ void PrintConfigDef::init_fff_params()
def = this->add("infill_acceleration", coFloat);
def->label = L("Infill");
def->tooltip = L("This is the acceleration your printer will use for infill. Set zero to disable "
"acceleration control for infill.");
"acceleration control for infill.");
def->sidetext = L("mm/s²");
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("solid_infill_acceleration", coFloat);
def->label = L("Solid infill");
def->tooltip = L("This is the acceleration your printer will use for solid infill. Set zero to use "
"the value for infill.");
def->sidetext = L("mm/s²");
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("top_solid_infill_acceleration", coFloat);
def->label = L("Top solid infill");
def->tooltip = L("This is the acceleration your printer will use for top solid infill. Set zero to use "
"the value for solid infill.");
def->sidetext = L("mm/s²");
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("infill_every_layers", coInt);
def->label = L("Combine infill every");
def->category = L("Infill");
@ -1950,6 +1961,14 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("external_perimeter_acceleration", coFloat);
def->label = L("External perimeters");
def->tooltip = L("This is the acceleration your printer will use for external perimeters. "
"Set zero to use the value for perimeters.");
def->sidetext = L("mm/s²");
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("perimeter_extruder", coInt);
def->label = L("Perimeter extruder");
def->category = L("Extruders");
@ -4025,6 +4044,22 @@ void PrintConfigDef::init_sla_params()
def->set_default_value(new ConfigOptionFloat(0.001));
}
// Ignore the following obsolete configuration keys:
static std::set<std::string> PrintConfigDef_ignore = {
"clip_multipart_objects",
"duplicate_x", "duplicate_y", "gcode_arcs", "multiply_x", "multiply_y",
"support_material_tool", "acceleration", "adjust_overhang_flow",
"standby_temperature", "scale", "rotate", "duplicate", "duplicate_grid",
"start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start",
"seal_position", "vibration_limit", "bed_size",
"print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe",
"serial_port", "serial_speed",
// Introduced in some PrusaSlicer 2.3.1 alpha, later renamed or removed.
"fuzzy_skin_perimeter_mode", "fuzzy_skin_shape",
// Introduced in PrusaSlicer 2.3.0-alpha2, later replaced by automatic calculation based on extrusion width.
"wall_add_middle_threshold", "wall_split_middle_threshold",
};
void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)
{
// handle legacy options
@ -4098,32 +4133,17 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
}
}*/
// Ignore the following obsolete configuration keys:
static std::set<std::string> ignore = {
"duplicate_x", "duplicate_y", "gcode_arcs", "multiply_x", "multiply_y",
"support_material_tool", "acceleration", "adjust_overhang_flow",
"standby_temperature", "scale", "rotate", "duplicate", "duplicate_grid",
"start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start",
"seal_position", "vibration_limit", "bed_size",
"print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe",
"serial_port", "serial_speed",
// Introduced in some PrusaSlicer 2.3.1 alpha, later renamed or removed.
"fuzzy_skin_perimeter_mode", "fuzzy_skin_shape",
// Introduced in PrusaSlicer 2.3.0-alpha2, later replaced by automatic calculation based on extrusion width.
"wall_add_middle_threshold", "wall_split_middle_threshold",
};
// In PrusaSlicer 2.3.0-alpha0 the "monotonous" infill was introduced, which was later renamed to "monotonic".
if (value == "monotonous" && (opt_key == "top_fill_pattern" || opt_key == "bottom_fill_pattern" || opt_key == "fill_pattern"))
value = "monotonic";
if (ignore.find(opt_key) != ignore.end()) {
opt_key = "";
if (PrintConfigDef_ignore.find(opt_key) != PrintConfigDef_ignore.end()) {
opt_key = {};
return;
}
if (! print_config_def.has(opt_key)) {
opt_key = "";
opt_key = {};
return;
}
}

View file

@ -487,7 +487,6 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloat, brim_separation))
((ConfigOptionEnum<BrimType>, brim_type))
((ConfigOptionFloat, brim_width))
((ConfigOptionBool, clip_multipart_objects))
((ConfigOptionBool, dont_support_bridges))
((ConfigOptionFloat, elefant_foot_compensation))
((ConfigOptionFloatOrPercent, extrusion_width))
@ -755,6 +754,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionInts, disable_fan_first_layers))
((ConfigOptionEnum<DraftShield>, draft_shield))
((ConfigOptionFloat, duplicate_distance))
((ConfigOptionFloat, external_perimeter_acceleration))
((ConfigOptionFloat, extruder_clearance_height))
((ConfigOptionFloat, extruder_clearance_radius))
((ConfigOptionStrings, extruder_colour))
@ -797,12 +797,14 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionInt, skirt_height))
((ConfigOptionInt, skirts))
((ConfigOptionInts, slowdown_below_layer_time))
((ConfigOptionFloat, solid_infill_acceleration))
((ConfigOptionBool, spiral_vase))
((ConfigOptionInt, standby_temperature_delta))
((ConfigOptionInts, temperature))
((ConfigOptionInt, threads))
((ConfigOptionPoints, thumbnails))
((ConfigOptionEnum<GCodeThumbnailsFormat>, thumbnails_format))
((ConfigOptionFloat, top_solid_infill_acceleration))
((ConfigOptionBools, wipe))
((ConfigOptionBool, wipe_tower))
((ConfigOptionFloat, wipe_tower_x))

View file

@ -424,8 +424,12 @@ void PrintObject::generate_support_spots()
float(this->print()->m_config.perimeter_acceleration.getFloat()),
this->config().raft_layers.getInt(), this->config().brim_type.value,
float(this->config().brim_width.getFloat())};
auto [supp_points, partial_objects] = SupportSpotsGenerator::full_search(this, cancel_func, params);
this->m_shared_regions->generated_support_points = {this->trafo_centered(), supp_points, partial_objects};
auto [supp_points, partial_objects] = SupportSpotsGenerator::full_search(this, cancel_func, params);
Transform3d po_transform = this->trafo_centered();
if (this->layer_count() > 0) {
po_transform = Geometry::translation_transform(Vec3d{0, 0, this->layers().front()->bottom_z()}) * po_transform;
}
this->m_shared_regions->generated_support_points = {po_transform, supp_points, partial_objects};
m_print->throw_if_canceled();
}
BOOST_LOG_TRIVIAL(debug) << "Searching support spots - end";
@ -620,8 +624,7 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "slicing_mode") {
steps.emplace_back(posSlice);
} else if (
opt_key == "clip_multipart_objects"
|| opt_key == "elefant_foot_compensation"
opt_key == "elefant_foot_compensation"
|| opt_key == "support_material_contact_distance"
|| opt_key == "xy_size_compensation") {
steps.emplace_back(posSlice);

View file

@ -1,9 +1,10 @@
#include "ClipperUtils.hpp"
#include "ElephantFootCompensation.hpp"
#include "I18N.hpp"
#include "Layer.hpp"
#include "MultiMaterialSegmentation.hpp"
#include "Print.hpp"
#include "ClipperUtils.hpp"
#include "ShortestPath.hpp"
#include <boost/log/trivial.hpp>
@ -237,9 +238,6 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
const PrintObjectRegions &print_object_regions,
const std::vector<float> &zs,
std::vector<VolumeSlices> &&volume_slices,
// If clipping is disabled, then ExPolygons produced by different volumes will never be merged, thus they will be allowed to overlap.
// It is up to the model designer to handle these overlaps.
const bool clip_multipart_objects,
const std::function<void()> &throw_on_cancel_callback)
{
model_volumes_sort_by_id(model_volumes);
@ -308,7 +306,7 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
}
tbb::parallel_for(
tbb::blocked_range<size_t>(0, zs_complex.size()),
[&slices_by_region, &print_object_regions, &zs_complex, &layer_ranges_regions_to_slices, clip_multipart_objects, &throw_on_cancel_callback]
[&slices_by_region, &print_object_regions, &zs_complex, &layer_ranges_regions_to_slices, &throw_on_cancel_callback]
(const tbb::blocked_range<size_t> &range) {
float z = zs_complex[range.begin()].second;
auto it_layer_range = layer_range_first(print_object_regions.layer_ranges, z);
@ -359,7 +357,7 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
if (next_region_same_modifier)
// To be used in the following iteration.
temp_slices[idx_region + 1].expolygons = std::move(source);
} else if ((region.model_volume->is_model_part() && clip_multipart_objects) || region.model_volume->is_negative_volume()) {
} else if (region.model_volume->is_model_part() || region.model_volume->is_negative_volume()) {
// Clip every non-zero region preceding it.
for (int idx_region2 = 0; idx_region2 < idx_region; ++ idx_region2)
if (! temp_slices[idx_region2].expolygons.empty()) {
@ -388,10 +386,7 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
merged = true;
}
}
// Don't unite the regions if ! clip_multipart_objects. In that case it is user's responsibility
// to handle region overlaps. Indeed, one may intentionally let the regions overlap to produce crossing perimeters
// for example.
if (merged && clip_multipart_objects)
if (merged)
expolygons = closing_ex(expolygons, float(scale_(EPSILON)));
slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons);
i = j;
@ -404,6 +399,10 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
return slices_by_region;
}
// Layer::slicing_errors is no more set since 1.41.1 or possibly earlier, thus this code
// was not really functional for a long day and nobody missed it.
// Could we reuse this fixing code one day?
/*
std::string fix_slicing_errors(LayerPtrs &layers, const std::function<void()> &throw_if_canceled)
{
// Collect layers with slicing errors.
@ -486,6 +485,7 @@ std::string fix_slicing_errors(LayerPtrs &layers, const std::function<void()> &t
"The model has overlapping or self-intersecting facets. I tried to repair it, "
"however you might want to check the results or repair the input file and retry.\n";
}
*/
// Called by make_perimeters()
// 1) Decides Z positions of the layers,
@ -508,12 +508,18 @@ void PrintObject::slice()
m_layers = new_layers(this, generate_object_layers(m_slicing_params, layer_height_profile));
this->slice_volumes();
m_print->throw_if_canceled();
#if 0
// Layer::slicing_errors is no more set since 1.41.1 or possibly earlier, thus this code
// was not really functional for a long day and nobody missed it.
// Could we reuse this fixing code one day?
// Fix the model.
//FIXME is this the right place to do? It is done repeateadly at the UI and now here at the backend.
std::string warning = fix_slicing_errors(m_layers, [this](){ m_print->throw_if_canceled(); });
m_print->throw_if_canceled();
if (! warning.empty())
BOOST_LOG_TRIVIAL(info) << warning;
#endif
// Update bounding boxes, back up raw slices of complex models.
tbb::parallel_for(
tbb::blocked_range<size_t>(0, m_layers.size()),
@ -696,7 +702,6 @@ void PrintObject::slice_volumes()
slice_volumes_inner(
print->config(), this->config(), this->trafo_centered(),
this->model_object()->volumes, m_shared_regions->layer_ranges, slice_zs, throw_on_cancel_callback),
m_config.clip_multipart_objects,
throw_on_cancel_callback);
for (size_t region_id = 0; region_id < region_slices.size(); ++ region_id) {
@ -806,8 +811,12 @@ void PrintObject::slice_volumes()
if (elephant_foot_compensation_scaled > 0.f && ! m_layers.empty()) {
// The Elephant foot has been compensated, therefore the 1st layer's lslices are shrank with the Elephant foot compensation value.
// Store the uncompensated value there.
assert(m_layers.front()->id() == 0);
m_layers.front()->lslices = std::move(lslices_1st_layer);
//FIXME is this operation needed? MMU painting and brim now have to do work arounds to work with compensated layer, not with the uncompensated layer.
// There may be subtle issues removing this block such as support raft sticking too well with the first object layer.
Layer &layer = *m_layers.front();
assert(layer.id() == 0);
layer.lslices = std::move(lslices_1st_layer);
layer.lslice_indices_sorted_by_print_order = chain_expolygons(layer.lslices);
}
}

View file

@ -1076,6 +1076,15 @@ std::vector<size_t> chain_points(const Points &points, Point *start_near)
return out;
}
std::vector<size_t> chain_expolygons(const ExPolygons &expolygons, Point *start_near)
{
Points ordering_points;
ordering_points.reserve(expolygons.size());
for (const ExPolygon &ex : expolygons)
ordering_points.push_back(ex.contour.first_point());
return chain_points(ordering_points);
}
#ifndef NDEBUG
// #define DEBUG_SVG_OUTPUT
#endif /* NDEBUG */

View file

@ -12,7 +12,11 @@ namespace ClipperLib { class PolyNode; }
namespace Slic3r {
class ExPolygon;
using ExPolygons = std::vector<ExPolygon>;
std::vector<size_t> chain_points(const Points &points, Point *start_near = nullptr);
std::vector<size_t> chain_expolygons(const ExPolygons &expolygons, Point *start_near = nullptr);
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
void reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const std::vector<std::pair<size_t, bool>> &chain);

View file

@ -269,7 +269,8 @@ void set_current_thread_qos()
#ifdef __APPLE__
// OSX specific: Set Quality of Service to "user initiated", so that the threads will be scheduled to high performance
// cores if available.
pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0);
// With QOS_CLASS_USER_INITIATED the worker threads drop priority once slicer loses user focus.
pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
#endif // __APPLE__
}

View file

@ -493,6 +493,9 @@ int GLVolumeCollection::load_wipe_tower_preview(
if (height == 0.0f)
height = 0.1f;
static const float brim_height = 0.2f;
const float scaled_brim_height = brim_height / height;
TriangleMesh mesh;
ColorRGBA color = ColorRGBA::DARK_YELLOW();
@ -506,8 +509,6 @@ int GLVolumeCollection::load_wipe_tower_preview(
depth = std::max(depth, 10.f);
float min_width = 30.f;
const float scaled_brim_height = 0.2f / height;
// We'll now create the box with jagged edge. y-coordinates of the pre-generated model
// are shifted so that the front edge has y=0 and centerline of the back edge has y=depth:
// We split the box in three main pieces,
@ -553,6 +554,10 @@ int GLVolumeCollection::load_wipe_tower_preview(
// central parts generator
auto generate_central = [&]() {
const std::vector<Vec3f> vertices = {
// this part is not watertight to avoid to have different geometries for the cases
// brim_width < 10.0
// brim_width == 10.0
// brim_width > 10.0
{ 38.453f, -(depth + brim_width), 0.0f },
{ 61.547f, -(depth + brim_width), 0.0f },
{ 38.453f, -(depth + brim_width), scaled_brim_height },
@ -562,33 +567,33 @@ int GLVolumeCollection::load_wipe_tower_preview(
{ 38.453f, -depth, 1.0f },
{ 61.547f, -depth, 1.0f },
{ 38.453f, 0.0f, 1.0f },
{ 38.453f + 0.57735f * brim_width, brim_width, 1.0f },
{ 44.2265f, 10.0f, 1.0f },
{ 50.0f - 0.57735f * brim_width, brim_width, 1.0f },
{ 50.0f, 0.0f, 1.0f },
{ 55.7735f, -10.0f, 1.0f },
{ 61.547f, 0.0f, 1.0f },
{ 38.453f, 0.0f, scaled_brim_height },
{ 38.453f, brim_width, scaled_brim_height },
{ 38.453f + 0.57735f * brim_width, brim_width, scaled_brim_height },
{ 50.0f - 0.57735f * brim_width, brim_width, scaled_brim_height },
{ 44.2265f, 10.0f, scaled_brim_height },
{ 50.0f, 0.0f, scaled_brim_height },
{ 55.7735f, -10.0f, scaled_brim_height },
{ 61.547f, 0.0f, scaled_brim_height },
{ 38.453f, 0.0f, 0.0f },
{ 44.2265f, 10.0f, 0.0f },
{ 50.0f, 0.0f, 0.0f },
{ 55.7735f, -10.0f, 0.0f },
{ 61.547f, 0.0f, 0.0f },
{ 38.453f, brim_width, scaled_brim_height },
{ 61.547f, brim_width, scaled_brim_height },
{ 38.453f, brim_width, 0.0f },
{ 38.453f + 0.57735f * brim_width, brim_width, 0.0f },
{ 44.2265f, 10.0f, 0.0f },
{ 50.0f - 0.57735f * brim_width, brim_width, 0.0f },
{ 61.547f, brim_width, 0.0f }
{ 61.547f, brim_width, 0.0f },
};
const std::vector<Vec3i> triangles = {
{ 0, 1, 3 }, { 0, 3, 2 }, { 2, 3, 5 }, { 2, 5, 4 }, { 4, 5, 7 }, { 4, 7, 6 }, { 7, 14, 13 }, { 7, 13, 6 },
{ 6, 13, 12 }, { 6, 12, 8 }, { 8, 12, 11 }, { 8, 11, 9 }, { 9, 11, 10 }, { 18, 19, 22 }, { 22, 19, 21 }, { 19, 20, 21 },
{ 15, 17, 16 }, { 17, 15, 8 }, { 17, 8, 9 }, { 21, 13, 14 }, { 21, 20, 13 }, { 20, 19, 12 }, { 20, 12, 13 }, { 19, 18, 11 },
{ 19, 11, 12 }, { 27, 26, 18 }, { 27, 18, 22 }, { 26, 25, 18 }, { 18, 25, 11 }, { 11, 25, 10 }, { 25, 24, 17 }, { 25, 17, 9 },
{ 25, 9, 10 }, { 24, 23, 16 }, { 24, 16, 17 }, { 1, 26, 27 }, { 1, 23, 26 }, { 1, 0, 23 }, { 0, 23, 24 }, { 24, 25, 26 }
{ 0, 1, 3 }, { 0, 3, 2 }, { 2, 3, 5 }, { 2, 5, 4 }, { 4, 5, 7 }, { 4, 7, 6 },
{ 6, 7, 11 }, { 6, 11, 10 }, { 6, 10, 8 }, { 8, 10, 9 }, { 11, 7, 12 }, { 14, 13, 8 },
{ 14, 8, 9 }, { 19, 18, 13 }, { 19, 13, 14 }, { 15, 14, 9 }, { 15, 9, 10 }, { 20, 19, 14 },
{ 20, 14, 15 }, { 16, 15, 10 }, { 16, 10, 11 }, { 21, 20, 15 }, { 21, 15, 16 }, { 17, 16, 11 },
{ 17, 11, 12 }, { 22, 21, 16 }, { 22, 16, 17 }, { 15, 16, 17 }, { 13, 15, 23 }, { 15, 17, 24 },
{ 15, 24, 23 }, { 26, 25, 23 }, { 26, 23, 24 }, { 0, 25, 1 }, { 1, 25, 26 }, { 20, 18, 19 }
};
indexed_triangle_set its;
@ -614,7 +619,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
// We have the mesh ready. It has one tooth and width of min_width. We will now
// append several of these together until we are close to the required width
// of the block. Than we can scale it precisely.
size_t n = std::max(1, int(width / min_width)); // How many shall be merged?
const size_t n = std::max(1, int(width / min_width)); // How many shall be merged?
for (size_t i = 0; i < n; ++i) {
mesh.merge(tooth_mesh);
tooth_mesh.translate(100.0f, 0.0f, 0.0f);
@ -695,8 +700,13 @@ int GLVolumeCollection::load_wipe_tower_preview(
mesh.merge(TriangleMesh(std::move(data)));
mesh.scale(Vec3f(width / (n * 100.0f), 1.0f, height)); // Scaling to proper width
}
else
mesh = make_cube(width, depth, height);
else {
mesh = make_cube(width, depth, height - brim_height);
mesh.translate(0.0f, 0.0f, brim_height);
TriangleMesh brim_mesh = make_cube(width + 2.0f * brim_width, depth + 2.0f * brim_width, brim_height);
brim_mesh.translate(-brim_width, -brim_width, 0.0f);
mesh.merge(brim_mesh);
}
volumes.emplace_back(new GLVolume(color));
GLVolume& v = *volumes.back();

View file

@ -261,7 +261,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
toggle_field(el, has_top_solid_infill || (has_spiral_vase && has_bottom_solid_infill));
bool have_default_acceleration = config->opt_float("default_acceleration") > 0;
for (auto el : { "perimeter_acceleration", "infill_acceleration",
for (auto el : { "perimeter_acceleration", "infill_acceleration", "top_solid_infill_acceleration",
"solid_infill_acceleration", "external_perimeter_acceleration"
"bridge_acceleration", "first_layer_acceleration" })
toggle_field(el, have_default_acceleration);

View file

@ -1315,10 +1315,12 @@ PageUpdate::PageUpdate(ConfigWizard *parent)
box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); });
}
namespace DownloaderUtils
{
namespace {
#ifdef _WIN32
wxString get_downloads_path()
{
wxString ret;
@ -1330,7 +1332,6 @@ namespace DownloaderUtils
CoTaskMemFree(path);
return ret;
}
#elif __APPLE__
wxString get_downloads_path()
{
@ -1348,9 +1349,8 @@ namespace DownloaderUtils
}
return wxString();
}
#endif
}
Worker::Worker(wxWindow* parent)
: wxBoxSizer(wxHORIZONTAL)
, m_parent(parent)
@ -1432,16 +1432,16 @@ PageDownloader::PageDownloader(ConfigWizard* parent)
)));
#endif
box_allow_downloads->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->downloader->allow(event.IsChecked()); });
box_allow_downloads->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->m_downloader->allow(event.IsChecked()); });
downloader = new DownloaderUtils::Worker(this);
append(downloader);
downloader->allow(box_allow_value);
m_downloader = new DownloaderUtils::Worker(this);
append(m_downloader);
m_downloader->allow(box_allow_value);
}
bool PageDownloader::on_finish_downloader() const
{
return downloader->on_finish();
return m_downloader->on_finish();
}
bool DownloaderUtils::Worker::perform_register(const std::string& path_override/* = {}*/)
@ -3035,9 +3035,11 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
#ifdef __linux__
// Desktop integration on Linux
BOOST_LOG_TRIVIAL(debug) << "ConfigWizard::priv::apply_config integrate_desktop" << page_welcome->integrate_desktop() << " perform_registration_linux " << page_downloader->downloader->get_perform_registration_linux();
if (page_welcome->integrate_desktop() || page_downloader->downloader->get_perform_registration_linux())
DesktopIntegrationDialog::perform_desktop_integration(page_downloader->downloader->get_perform_registration_linux());
BOOST_LOG_TRIVIAL(debug) << "ConfigWizard::priv::apply_config integrate_desktop" << page_welcome->integrate_desktop() << " perform_registration_linux " << page_downloader->m_downloader->get_perform_registration_linux();
if (page_welcome->integrate_desktop())
DesktopIntegrationDialog::perform_desktop_integration();
if (page_downloader->m_downloader->get_perform_registration_linux())
DesktopIntegrationDialog::perform_downloader_desktop_integration();
#endif
// Decide whether to create snapshot based on run_reason and the reset profile checkbox
@ -3175,7 +3177,8 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
// apply materials in app_config
for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS})
app_config->set_section(section_name, appconfig_new.get_section(section_name));
if (appconfig_new.has_section(section_name))
app_config->set_section(section_name, appconfig_new.get_section(section_name));
app_config->set_vendors(appconfig_new);

View file

@ -4,6 +4,8 @@
#include <memory>
#include <wx/dialog.h>
#include <wx/sizer.h>
#include <wx/textctrl.h>
#include "GUI_Utils.hpp"
@ -14,6 +16,36 @@ class PresetUpdater;
namespace GUI {
namespace DownloaderUtils {
class Worker : public wxBoxSizer
{
wxWindow* m_parent{ nullptr };
wxTextCtrl* m_input_path{ nullptr };
bool downloader_checked{ false };
#ifdef __linux__
bool perform_registration_linux{ false };
#endif // __linux__
void deregister();
public:
Worker(wxWindow* parent);
~Worker() {}
void allow(bool allow_) { downloader_checked = allow_; }
bool is_checked() const { return downloader_checked; }
wxString path_name() const { return m_input_path ? m_input_path->GetValue() : wxString(); }
void set_path_name(wxString name);
void set_path_name(const std::string& name);
bool on_finish();
bool perform_register(const std::string& path_override = {});
#ifdef __linux__
bool get_perform_registration_linux() { return perform_registration_linux; }
#endif // __linux__
};
}
class ConfigWizard: public DPIDialog
{

View file

@ -10,12 +10,10 @@
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#include <wx/sizer.h>
#include <wx/panel.h>
#include <wx/button.h>
#include <wx/choice.h>
#include <wx/spinctrl.h>
#include <wx/textctrl.h>
#include <wx/listbox.h>
#include <wx/checklst.h>
#include <wx/radiobut.h>
@ -418,44 +416,10 @@ struct PageUpdate: ConfigWizardPage
PageUpdate(ConfigWizard *parent);
};
namespace DownloaderUtils {
wxString get_downloads_path();
class Worker : public wxBoxSizer
{
wxWindow* m_parent {nullptr};
wxTextCtrl* m_input_path {nullptr};
bool downloader_checked {false};
#ifdef __linux__
bool perform_registration_linux { false };
#endif // __linux__
void deregister();
public:
Worker(wxWindow* parent);
~Worker(){}
void allow(bool allow_) { downloader_checked = allow_; }
bool is_checked() const { return downloader_checked; }
wxString path_name() const { return m_input_path ? m_input_path->GetValue() : wxString(); }
void set_path_name(wxString name);
void set_path_name(const std::string& name);
bool on_finish();
bool perform_register(const std::string& path_override = {});
#ifdef __linux__
bool get_perform_registration_linux() { return perform_registration_linux; }
#endif // __linux__
};
}
struct PageDownloader : ConfigWizardPage
{
DownloaderUtils::Worker* downloader{ nullptr };
DownloaderUtils::Worker* m_downloader { nullptr };
PageDownloader(ConfigWizard* parent);

View file

@ -218,9 +218,9 @@ bool DesktopIntegrationDialog::integration_possible()
{
return true;
}
void DesktopIntegrationDialog::perform_desktop_integration(bool perform_downloader)
void DesktopIntegrationDialog::perform_desktop_integration()
{
BOOST_LOG_TRIVIAL(debug) << "performing desktop integration. With downloader integration: " << perform_downloader;
BOOST_LOG_TRIVIAL(debug) << "performing desktop integration.";
// Path to appimage
const char *appimage_env = std::getenv("APPIMAGE");
std::string excutable_path;
@ -423,38 +423,6 @@ void DesktopIntegrationDialog::perform_desktop_integration(bool perform_download
show_error(nullptr, _L("Performing desktop integration failed - could not create Gcodeviewer desktop file. PrusaSlicer desktop file was probably created successfully."));
}
}
if (perform_downloader)
{
std::string desktop_file_downloader = GUI::format(
"[Desktop Entry]\n"
"Name=PrusaSlicer URL Protocol%1%\n"
"Exec=\"%3%\" --single-instance %%u\n"
"Icon=PrusaSlicer%4%\n"
"Terminal=false\n"
"Type=Application\n"
"MimeType=x-scheme-handler/prusaslicer;\n"
"StartupNotify=false\n"
, name_suffix, version_suffix, excutable_path, version_suffix);
// desktop file for downloader as part of main app
std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
if (create_desktop_file(desktop_path, desktop_file_downloader)) {
// save path to desktop file
app_config->set("desktop_integration_URL_path", desktop_path);
// finish registration on mime type
std::string command = GUI::format("xdg-mime default PrusaSlicerURLProtocol%1%.desktop x-scheme-handler/prusaslicer", version_suffix);
BOOST_LOG_TRIVIAL(debug) << "system command: " << command;
int r = system(command.c_str());
BOOST_LOG_TRIVIAL(debug) << "system result: " << r;
} else {
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create URL Protocol desktop file";
show_error(nullptr, _L("Performing desktop integration failed - could not create URL Protocol desktop file."));
return;
}
}
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess);
}
void DesktopIntegrationDialog::undo_desktop_intgration()
@ -487,15 +455,162 @@ void DesktopIntegrationDialog::undo_desktop_intgration()
std::remove(path.c_str());
}
}
// URL Protocol
path = std::string(app_config->get("desktop_integration_URL_path"));
if (!path.empty()) {
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
std::remove(path.c_str());
}
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationSuccess);
}
void DesktopIntegrationDialog::perform_downloader_desktop_integration()
{
BOOST_LOG_TRIVIAL(debug) << "performing downloader desktop integration.";
// Path to appimage
const char* appimage_env = std::getenv("APPIMAGE");
std::string excutable_path;
if (appimage_env) {
try {
excutable_path = boost::filesystem::canonical(boost::filesystem::path(appimage_env)).string();
}
catch (std::exception&) {
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed - boost::filesystem::canonical did not return appimage path.";
show_error(nullptr, _L("Performing downloader desktop integration failed - boost::filesystem::canonical did not return appimage path."));
return;
}
}
else {
// not appimage - find executable
excutable_path = boost::dll::program_location().string();
//excutable_path = wxStandardPaths::Get().GetExecutablePath().string();
BOOST_LOG_TRIVIAL(debug) << "non-appimage path to executable: " << excutable_path;
if (excutable_path.empty())
{
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed - no executable found.";
show_error(nullptr, _L("Performing downloader desktop integration failed - Could not find executable."));
return;
}
}
// Escape ' characters in appimage, other special symbols will be esacaped in desktop file by 'excutable_path'
//boost::replace_all(excutable_path, "'", "'\\''");
excutable_path = escape_string(excutable_path);
// Find directories icons and applications
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
// $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data files in addition to the $XDG_DATA_HOME base directory.
// The directories in $XDG_DATA_DIRS should be seperated with a colon ':'.
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
std::vector<std::string>target_candidates;
resolve_path_from_var("XDG_DATA_HOME", target_candidates);
resolve_path_from_var("XDG_DATA_DIRS", target_candidates);
AppConfig* app_config = wxGetApp().app_config;
// suffix string to create different desktop file for alpha, beta.
std::string version_suffix;
std::string name_suffix;
std::string version(SLIC3R_VERSION);
if (version.find("alpha") != std::string::npos)
{
version_suffix = "-alpha";
name_suffix = " - alpha";
}
else if (version.find("beta") != std::string::npos)
{
version_suffix = "-beta";
name_suffix = " - beta";
}
// theme path to icon destination
std::string icon_theme_path;
std::string icon_theme_dirs;
if (platform_flavor() == PlatformFlavor::LinuxOnChromium) {
icon_theme_path = "hicolor/96x96/apps/";
icon_theme_dirs = "/hicolor/96x96/apps";
}
std::string target_dir_desktop;
// desktop file
// iterate thru target_candidates to find applications folder
std::string desktop_file_downloader = GUI::format(
"[Desktop Entry]\n"
"Name=PrusaSlicer URL Protocol%1%\n"
"Exec=\"%2%\" --single-instance %%u\n"
"Terminal=false\n"
"Type=Application\n"
"MimeType=x-scheme-handler/prusaslicer;\n"
"StartupNotify=false\n"
"NoDisplay=true\n"
, name_suffix, excutable_path);
// desktop file for downloader as part of main app
std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
if (create_desktop_file(desktop_path, desktop_file_downloader)) {
// save path to desktop file
app_config->set("desktop_integration_URL_path", desktop_path);
// finish registration on mime type
std::string command = GUI::format("xdg-mime default PrusaSlicerURLProtocol%1%.desktop x-scheme-handler/prusaslicer", version_suffix);
BOOST_LOG_TRIVIAL(debug) << "system command: " << command;
int r = system(command.c_str());
BOOST_LOG_TRIVIAL(debug) << "system result: " << r;
}
bool candidate_found = false;
for (size_t i = 0; i < target_candidates.size(); ++i) {
if (contains_path_dir(target_candidates[i], "applications")) {
target_dir_desktop = target_candidates[i];
// Write slicer desktop file
std::string path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
if (create_desktop_file(path, desktop_file_downloader)) {
app_config->set("desktop_integration_URL_path", path);
candidate_found = true;
BOOST_LOG_TRIVIAL(debug) << "PrusaSlicerURLProtocol.desktop file installation success.";
break;
}
else {
// write failed - try another path
BOOST_LOG_TRIVIAL(debug) << "Attempt to PrusaSlicerURLProtocol.desktop file installation failed. failed path: " << target_candidates[i];
target_dir_desktop.clear();
}
}
}
// if all failed - try creating default home folder
if (!candidate_found) {
// create $HOME/.local/share
create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications");
// create desktop file
target_dir_desktop = GUI::format("%1%/.local/share", wxFileName::GetHomeDir());
std::string path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
if (contains_path_dir(target_dir_desktop, "applications")) {
if (!create_desktop_file(path, desktop_file_downloader)) {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed - could not create desktop file.";
return;
}
app_config->set("desktop_integration_URL_path", path);
}
else {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed because the application directory was not found.";
return;
}
}
assert(!target_dir_desktop.empty());
if (target_dir_desktop.empty()) {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed because the application directory was not found.";
show_error(nullptr, _L("Performing downloader desktop integration failed because the application directory was not found."));
return;
}
// finish registration on mime type
std::string command = GUI::format("xdg-mime default PrusaSlicerURLProtocol%1%.desktop x-scheme-handler/prusaslicer", version_suffix);
BOOST_LOG_TRIVIAL(debug) << "system command: " << command;
int r = system(command.c_str());
BOOST_LOG_TRIVIAL(debug) << "system result: " << r;
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess);
}
void DesktopIntegrationDialog::undo_downloader_registration()
{
const AppConfig *app_config = wxGetApp().app_config;
@ -532,7 +647,7 @@ DesktopIntegrationDialog::DesktopIntegrationDialog(wxWindow *parent)
wxButton *btn_perform = new wxButton(this, wxID_ANY, _L("Perform"));
btn_szr->Add(btn_perform, 0, wxALL, 10);
btn_perform->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::perform_desktop_integration(false); EndModal(wxID_ANY); });
btn_perform->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::perform_desktop_integration(); EndModal(wxID_ANY); });
if (can_undo){
wxButton *btn_undo = new wxButton(this, wxID_ANY, _L("Undo"));

View file

@ -29,10 +29,11 @@ public:
// if perform_downloader:
// Creates Destktop files for PrusaSlicer downloader feature
// Regiters PrusaSlicer to start on prusaslicer:// URL
static void perform_desktop_integration(bool perform_downloader);
static void perform_desktop_integration();
// Deletes Desktop files and icons for both PrusaSlicer and GcodeViewer at paths stored in App Config.
static void undo_desktop_intgration();
static void perform_downloader_desktop_integration();
static void undo_downloader_registration();
private:

View file

@ -137,13 +137,30 @@ void FileGet::priv::get_perform()
std::string extension = boost::filesystem::extension(dest_path);
std::string just_filename = m_filename.substr(0, m_filename.size() - extension.size());
std::string final_filename = just_filename;
size_t version = 0;
while (boost::filesystem::exists(m_dest_folder / (final_filename + extension)) || boost::filesystem::exists(m_dest_folder / (final_filename + extension + "." + std::to_string(get_current_pid()) + ".download")))
// Find unsed filename
try {
size_t version = 0;
while (boost::filesystem::exists(m_dest_folder / (final_filename + extension)) || boost::filesystem::exists(m_dest_folder / (final_filename + extension + "." + std::to_string(get_current_pid()) + ".download")))
{
++version;
if (version > 999) {
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
evt->SetString(GUI::format_wxstr(L"Failed to find suitable filename. Last name: %1%." , (m_dest_folder / (final_filename + extension)).string()));
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
return;
}
final_filename = GUI::format("%1%(%2%)", just_filename, std::to_string(version));
}
} catch (const boost::filesystem::filesystem_error& e)
{
++version;
final_filename = just_filename + "(" + std::to_string(version) + ")";
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
evt->SetString(e.what());
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
return;
}
m_filename = final_filename + extension;
m_tmp_path = m_dest_folder / (m_filename + "." + std::to_string(get_current_pid()) + ".download");

View file

@ -172,16 +172,20 @@ FileArchiveDialog::FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* ar
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX)
, m_selected_paths (selected_paths)
{
#ifdef _WIN32
wxGetApp().UpdateDarkUI(this);
#else
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
#endif
int em = em_unit();
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
m_avc = new ArchiveViewCtrl(this, wxSize(60 * em, 30 * em));
m_avc->AppendToggleColumn(L"\u2714", 0, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em);
m_avc = new ArchiveViewCtrl(this, wxSize(45 * em, 30 * em));
wxDataViewColumn* toggle_column = m_avc->AppendToggleColumn(L"\u2714", 0, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em);
m_avc->AppendTextColumn("filename", 1);
std::vector<std::shared_ptr<ArchiveViewNode>> stack;
std::function<void(std::vector<std::shared_ptr<ArchiveViewNode> >&, size_t)> reduce_stack = [] (std::vector<std::shared_ptr<ArchiveViewNode>>& stack, size_t size) {
@ -233,40 +237,51 @@ FileArchiveDialog::FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* ar
}
// sorting files will help adjust_stack function to not create multiple same folders
std::sort(filtered_entries.begin(), filtered_entries.end(), [](const boost::filesystem::path& p1, const boost::filesystem::path& p2){ return p1.string() > p2.string(); });
size_t entry_count = 0;
size_t depth = 1;
for (const boost::filesystem::path& path : filtered_entries)
{
std::shared_ptr<ArchiveViewNode> parent(nullptr);
adjust_stack(path, stack);
depth = std::max(depth, adjust_stack(path, stack));
if (!stack.empty())
parent = stack.back();
if (std::regex_match(path.extension().string(), pattern_drop)) { // this leaves out non-compatible files
m_avc->get_model()->AddFile(parent, GUI::format_wxstr(path.filename().string()), false)->set_fullpath(/*std::move(path)*/path); // filename string to wstring?
entry_count++;
}
}
if (entry_count == 1)
on_all_button();
toggle_column->SetWidth((4 + depth) * em);
wxBoxSizer* btn_sizer = new wxBoxSizer(wxHORIZONTAL);
wxButton* btn_all = new wxButton(this, wxID_ANY, "All");
wxButton* btn_all = new wxButton(this, wxID_ANY, _L("All"));
btn_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_all_button(); });
btn_sizer->Add(btn_all, 0, wxLeft);
btn_sizer->Add(btn_all, 0);
wxButton* btn_none = new wxButton(this, wxID_ANY, "None");
wxButton* btn_none = new wxButton(this, wxID_ANY, _L("None"));
btn_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_none_button(); });
btn_sizer->Add(btn_none, 0, wxLeft);
btn_sizer->Add(btn_none, 0, wxLEFT, em);
btn_sizer->AddStretchSpacer();
wxButton* btn_run = new wxButton(this, wxID_OK, "Open");
wxButton* btn_run = new wxButton(this, wxID_OK, _L("Open"));
btn_run->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_open_button(); });
btn_sizer->Add(btn_run, 0, wxRIGHT);
btn_sizer->Add(btn_run, 0, wxRIGHT, em);
wxButton* cancel_btn = new wxButton(this, wxID_CANCEL, "Cancel");
wxButton* cancel_btn = new wxButton(this, wxID_CANCEL, _L("Cancel"));
cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { this->EndModal(wxID_CANCEL); });
btn_sizer->Add(cancel_btn, 0, wxRIGHT);
btn_sizer->Add(cancel_btn, 0, wxRIGHT, em);
topSizer->Add(m_avc, 1, wxEXPAND | wxALL, 10);
topSizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 10);
this->SetMinSize(wxSize(80 * em, 30 * em));
this->SetSizer(topSizer);
SetMinSize(wxSize(40 * em, 30 * em));
for (const wxString& id : {_L("All"), _L("None"), _L("Open"), _L("Cancel") })
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(FindWindowByLabel(id, this)));
}
void FileArchiveDialog::on_dpi_changed(const wxRect& suggested_rect)
@ -277,9 +292,8 @@ void FileArchiveDialog::on_dpi_changed(const wxRect& suggested_rect)
//for (auto btn : { m_save_btn, m_transfer_btn, m_discard_btn })
// if (btn) btn->msw_rescale();
const wxSize& size = wxSize(70 * em, 30 * em);
SetMinSize(size);
const wxSize& size = wxSize(45 * em, 40 * em);
SetSize(size);
//m_tree->Rescale(em);
Fit();

View file

@ -3972,8 +3972,9 @@ void GLCanvas3D::update_sequential_clearance()
Pointf3s& cache_hull_2d = m_sequential_print_clearance.m_hull_2d_cache.emplace_back(Pointf3s());
cache_hull_2d.reserve(hull_2d.points.size());
const Transform3d inv_trafo = trafo.get_matrix().inverse();
for (const Point& p : hull_2d.points) {
cache_hull_2d.emplace_back(unscale<double>(p.x()), unscale<double>(p.y()), 0.0);
cache_hull_2d.emplace_back(inv_trafo * Vec3d(unscale<double>(p.x()), unscale<double>(p.y()), 0.0));
}
}
m_sequential_print_clearance_first_displacement = false;
@ -7055,7 +7056,7 @@ void GLCanvas3D::GizmoHighlighter::init(GLGizmosManager* manager, GLGizmosManage
{
if (m_timer.IsRunning())
invalidate();
if (!gizmo || !canvas)
if (gizmo == GLGizmosManager::EType::Undefined || !canvas)
return;
m_timer.Start(300, false);

View file

@ -79,7 +79,6 @@
#include "DesktopIntegrationDialog.hpp"
#include "SendSystemInfoDialog.hpp"
#include "Downloader.hpp"
#include "ConfigWizard_private.hpp"
#include "BitmapCache.hpp"
#include "Notebook.hpp"
@ -2890,6 +2889,7 @@ void GUI_App::MacOpenURL(const wxString& url)
{
if (app_config && !app_config->get_bool("downloader_url_registered"))
{
notification_manager()->push_notification(NotificationType::URLNotRegistered);
BOOST_LOG_TRIVIAL(error) << "Recieved command to open URL, but it is not allowed in app configuration. URL: " << url;
return;
}
@ -3081,11 +3081,11 @@ void GUI_App::show_downloader_registration_dialog()
), SLIC3R_APP_NAME, SLIC3R_VERSION)
, true, wxYES_NO);
if (msg.ShowModal() == wxID_YES) {
auto downloader = new DownloaderUtils::Worker(nullptr);
downloader->perform_register(app_config->get("url_downloader_dest"));
auto downloader_worker = new DownloaderUtils::Worker(nullptr);
downloader_worker->perform_register(app_config->get("url_downloader_dest"));
#ifdef __linux__
if (downloader->get_perform_registration_linux())
DesktopIntegrationDialog::perform_desktop_integration(true);
if (downloader_worker->get_perform_registration_linux())
DesktopIntegrationDialog::perform_downloader_desktop_integration();
#endif // __linux__
} else {
app_config->set("downloader_url_registered", "0");

View file

@ -306,8 +306,8 @@ public:
Plater* plater();
const Plater* plater() const;
Model& model();
NotificationManager * notification_manager();
GalleryDialog * gallery_dialog();
NotificationManager* notification_manager();
GalleryDialog * gallery_dialog();
Downloader* downloader();
// Parameters extracted from the command line to be passed to GUI after initialization.

View file

@ -244,7 +244,7 @@ std::string GLGizmoCut3D::get_tooltip() const
if (m_hover_id == Z || (m_dragging && m_hover_id == CutPlane)) {
double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0;
std::string unit_str = " " + (m_imperial_units ? _u8L("inch") : _u8L("mm"));
const BoundingBoxf3 tbb = transformed_bounding_box(m_plane_center);
const BoundingBoxf3& tbb = m_transformed_bounding_box;
if (tbb.max.z() >= 0.0) {
double top = (tbb.min.z() <= 0.0 ? tbb.max.z() : tbb.size().z()) * koef;
tooltip += format(top, 2) + " " + unit_str + " (" + _u8L("Top part") + ")";
@ -401,7 +401,7 @@ bool GLGizmoCut3D::is_looking_forward() const
void GLGizmoCut3D::update_clipper()
{
BoundingBoxf3 box = bounding_box();
BoundingBoxf3 box = m_bounding_box;
// update cut_normal
Vec3d beg, end = beg = m_plane_center;
@ -549,7 +549,7 @@ bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& v
return !is_approx(old_val, value);
};
const BoundingBoxf3 bbox = bounding_box();
const BoundingBoxf3 bbox = m_bounding_box;
const float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z()) / 9.0) * (m_imperial_units ? f_mm_to_in : 1.f);
ImGuiWrapper::text(label);
@ -795,7 +795,7 @@ void GLGizmoCut3D::render_cut_plane_grabbers()
const Transform3d view_matrix = wxGetApp().plater()->get_camera().get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m;
const double mean_size = get_grabber_mean_size(bounding_box());
const double mean_size = get_grabber_mean_size(m_bounding_box);
double size;
const bool dragging_by_cut_plane = m_dragging && m_hover_id == CutPlane;
@ -1033,7 +1033,7 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform()
else if (!cut_line_processing()){
const Transform3d trafo = translation_transform(m_plane_center) * m_rotation_m;
const BoundingBoxf3 box = bounding_box();
const BoundingBoxf3 box = m_bounding_box;
const double size = get_half_size(get_grabber_mean_size(box));
Vec3d scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size);
@ -1068,25 +1068,16 @@ bool GLGizmoCut3D::on_is_activable() const
if (object_idx < 0 || selection.is_wipe_tower())
return false;
bool is_dowel_object = false;
if (const ModelObject* mo = wxGetApp().plater()->model().objects[object_idx]; mo->is_cut()) {
int solid_connector_cnt = 0;
int connectors_cnt = 0;
for (const ModelVolume* volume : mo->volumes) {
if (volume->is_cut_connector()) {
connectors_cnt++;
if (volume->is_model_part())
solid_connector_cnt++;
}
if (connectors_cnt > 1)
break;
}
is_dowel_object = connectors_cnt == 1 && solid_connector_cnt == 1;
if (const ModelObject* mo = wxGetApp().plater()->model().objects[object_idx];
mo->is_cut() && mo->volumes.size() == 1) {
const ModelVolume* volume = mo->volumes[0];
if (volume->is_cut_connector() && volume->cut_info.connector_type == CutConnectorType::Dowel)
return false;
}
// This is assumed in GLCanvas3D::do_rotate, do not change this
// without updating that function too.
return selection.is_single_full_instance() && !is_dowel_object && !m_parent.is_layers_editing_enabled();
return selection.is_single_full_instance() && !m_parent.is_layers_editing_enabled();
}
bool GLGizmoCut3D::on_is_selectable() const
@ -1192,7 +1183,11 @@ void GLGizmoCut3D::dragging_grabber_xy(const GLGizmoBase::UpdateData &data)
Vec3d rotation = Vec3d::Zero();
rotation[m_hover_id] = theta;
m_rotation_m = m_start_dragging_m * rotation_transform(rotation);
const Transform3d rotation_tmp = m_start_dragging_m * rotation_transform(rotation);
if (m_rotation_m.rotation() != rotation_tmp.rotation())
m_transformed_bounding_box = transformed_bounding_box(m_plane_center);
m_rotation_m = rotation_tmp;
m_angle = theta;
while (m_angle > two_pi)
@ -1254,9 +1249,13 @@ void GLGizmoCut3D::on_stop_dragging()
void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool force/* = false*/)
{
if (m_plane_center == center_pos)
return;
bool can_set_center_pos = force;
BoundingBoxf3 tbb;
if (!can_set_center_pos) {
const BoundingBoxf3 tbb = transformed_bounding_box(center_pos);
tbb = transformed_bounding_box(center_pos);
if (tbb.max.z() > -1. && tbb.min.z() < 1.)
can_set_center_pos = true;
else {
@ -1269,6 +1268,7 @@ void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool force/* = false*
}
if (can_set_center_pos) {
m_transformed_bounding_box = tbb;
m_plane_center = center_pos;
m_center_offset = m_plane_center - m_bb_center;
}
@ -1288,7 +1288,7 @@ BoundingBoxf3 GLGizmoCut3D::bounding_box() const
return ret;
}
BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(const Vec3d& plane_center, bool revert_move /*= false*/) const
BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(const Vec3d& plane_center) const
{
// #ysFIXME !!!
BoundingBoxf3 ret;
@ -1308,10 +1308,7 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(const Vec3d& plane_center,
Vec3d cut_center_offset = plane_center - instance_offset;
cut_center_offset[Z] -= sel_info->get_sla_shift();
const auto move = translation_transform(-cut_center_offset);
const auto move2 = translation_transform(plane_center);
const auto cut_matrix = (revert_move ? move2 : Transform3d::Identity()) * m_rotation_m.inverse() * move;
const auto cut_matrix = Transform3d::Identity() * m_rotation_m.inverse() * translation_transform(-cut_center_offset);
const Selection& selection = m_parent.get_selection();
const Selection::IndicesList& idxs = selection.get_volume_idxs();
@ -1344,6 +1341,8 @@ bool GLGizmoCut3D::update_bb()
const BoundingBoxf3 box = bounding_box();
if (m_max_pos != box.max || m_min_pos != box.min) {
m_bounding_box = box;
invalidate_cut_plane();
m_max_pos = box.max;
@ -1397,7 +1396,7 @@ void GLGizmoCut3D::init_picking_models()
}
if (!m_plane.model.is_initialized() && !m_hide_cut_plane && !m_connectors_editing) {
const double cp_width = 0.02 * get_grabber_mean_size(bounding_box());
const double cp_width = 0.02 * get_grabber_mean_size(m_bounding_box);
indexed_triangle_set its = its_make_frustum_dowel((double)m_cut_plane_radius_koef * m_radius, cp_width, m_cut_plane_as_circle ? 180 : 4);
m_plane.model.init_from(its);
m_plane.mesh_raycaster = std::make_unique<MeshRaycaster>(std::make_shared<const TriangleMesh>(std::move(its)));
@ -1640,9 +1639,8 @@ void GLGizmoCut3D::render_build_size()
{
double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0;
wxString unit_str = " " + (m_imperial_units ? _L("in") : _L("mm"));
const BoundingBoxf3 tbb = transformed_bounding_box(m_plane_center);
Vec3d tbb_sz = tbb.size();
Vec3d tbb_sz = m_transformed_bounding_box.size();
wxString size = "X: " + double_to_string(tbb_sz.x() * koef, 2) + unit_str +
", Y: " + double_to_string(tbb_sz.y() * koef, 2) + unit_str +
", Z: " + double_to_string(tbb_sz.z() * koef, 2) + unit_str;
@ -1655,7 +1653,7 @@ void GLGizmoCut3D::render_build_size()
void GLGizmoCut3D::reset_cut_plane()
{
set_center(bounding_box().center());
set_center(m_bb_center);
m_rotation_m = Transform3d::Identity();
m_angle_arc.reset();
update_clipper();
@ -1755,7 +1753,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
const bool has_connectors = !connectors.empty();
const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && bounding_box().center() == m_plane_center;
const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center == m_plane_center;
m_imgui->disabled_begin(is_cut_plane_init);
if (render_reset_button("cut_plane", _u8L("Reset cutting plane")))
reset_cut_plane();
@ -1847,10 +1845,9 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
add_vertical_scaled_interval(0.75f);
m_imgui->disabled_begin(has_connectors);
add_horizontal_shift(m_imgui->scaled(/*1*/.2f));
ImGuiWrapper::text(_L("Cut to") + ":");
ImGui::SameLine();
add_horizontal_scaled_interval(1.2f);
if (m_imgui->radio_button(_L("Objects"), !m_keep_as_parts))
m_keep_as_parts = false;
ImGui::SameLine();
@ -1999,38 +1996,6 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit)
render_debug_input_window(x);
}
// get volume transformation regarding to the "border". Border is related from the size of connectors
Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) const
{
bool is_prizm_dowel = m_connector_type == CutConnectorType::Dowel && m_connector_style == size_t(CutConnectorStyle::Prizm);
#if ENABLE_WORLD_COORDINATE
const Transform3d connector_trafo = is_prizm_dowel ?
Geometry::translation_transform(-m_connector_depth_ratio * Vec3d::UnitZ()) * m_rotation_m * Geometry::scale_transform({ 0.5 * m_connector_size, 0.5 * m_connector_size, 2 * m_connector_depth_ratio }) :
m_rotation_m * Geometry::scale_transform({ 0.5 * m_connector_size, 0.5 * m_connector_size, m_connector_depth_ratio });
#else
const Transform3d connector_trafo = assemble_transform(
is_prizm_dowel ? Vec3d(0.0, 0.0, -m_connector_depth_ratio) : Vec3d::Zero(),
Transformation(m_rotation_m).get_rotation(),
Vec3d(0.5*m_connector_size, 0.5*m_connector_size, is_prizm_dowel ? 2 * m_connector_depth_ratio : m_connector_depth_ratio),
Vec3d::Ones());
#endif // ENABLE_WORLD_COORDINATE
const Vec3d connector_bb = m_connector_mesh.transformed_bounding_box(connector_trafo).size();
const Vec3d bb = volume->mesh().bounding_box().size();
// calculate an unused border - part of the the volume, where we can't put connectors
const Vec3d border_scale(connector_bb.x() / bb.x(), connector_bb.y() / bb.y(), connector_bb.z() / bb.z());
const Transform3d vol_matrix = volume->get_matrix();
const Vec3d vol_trans = vol_matrix.translation();
// offset of the volume will be changed after scaling, so calculate the needed offset and set it to a volume_trafo
const Vec3d offset(vol_trans.x() * border_scale.x(), vol_trans.y() * border_scale.y(), vol_trans.z() * border_scale.z());
// scale and translate volume to suppress to put connectors too close to the border
return translation_transform(offset) * scale_transform(Vec3d::Ones() - border_scale) * vol_matrix;
}
bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos)
{
// check if connector pos is out of clipping plane
@ -2081,7 +2046,7 @@ bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& co
const BoundingBoxf3 cur_tbb = m_shapes[cur_connector.attribs].model.get_bounding_box().transformed(matrix);
// check if connector's bounding box is inside the object's bounding box
if (!bounding_box().contains(cur_tbb)) {
if (!m_bounding_box.contains(cur_tbb)) {
m_info_stats.outside_bb++;
return true;
}
@ -2572,8 +2537,7 @@ CommonGizmosDataID GLGizmoCut3D::on_get_requirements() const {
return CommonGizmosDataID(
int(CommonGizmosDataID::SelectionInfo)
| int(CommonGizmosDataID::InstancesHider)
| int(CommonGizmosDataID::ObjectClipper)
| int(CommonGizmosDataID::Raycaster));
| int(CommonGizmosDataID::ObjectClipper));
}
void GLGizmoCut3D::data_changed()

View file

@ -43,6 +43,9 @@ class GLGizmoCut3D : public GLGizmoBase
Vec3d m_bb_center{ Vec3d::Zero() };
Vec3d m_center_offset{ Vec3d::Zero() };
BoundingBoxf3 m_bounding_box;
BoundingBoxf3 m_transformed_bounding_box;
// values from RotationGizmo
double m_radius{ 0.0 };
double m_grabber_radius{ 0.0 };
@ -193,7 +196,7 @@ public:
void invalidate_cut_plane();
BoundingBoxf3 bounding_box() const;
BoundingBoxf3 transformed_bounding_box(const Vec3d& plane_center, bool revert_move = false) const;
BoundingBoxf3 transformed_bounding_box(const Vec3d& plane_center) const;
protected:
bool on_init() override;
@ -263,7 +266,6 @@ private:
void render_connect_mode_radio_button(CutConnectorMode mode);
bool render_reset_button(const std::string& label_id, const std::string& tooltip) const;
bool render_connect_type_radio_button(CutConnectorType type);
Transform3d get_volume_transformation(const ModelVolume* volume) const;
bool is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos);
bool is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos);
void render_connectors();

View file

@ -519,6 +519,13 @@ bool GLGizmoFdmSupports::has_backend_supports()
void GLGizmoFdmSupports::auto_generate()
{
std::string err = wxGetApp().plater()->fff_print().validate();
if (!err.empty()) {
MessageDialog dlg(GUI::wxGetApp().plater(), _L("Automatic painting requires valid print setup. \n") + from_u8(err), _L("Warning"), wxOK);
dlg.ShowModal();
return;
}
ModelObject *mo = m_c->selection_info()->model_object();
bool not_painted = std::all_of(mo->volumes.begin(), mo->volumes.end(), [](const ModelVolume* vol){
return vol->type() != ModelVolumeType::MODEL_PART || vol->supported_facets.empty();

View file

@ -239,10 +239,10 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection)
m_bounding_box = box;
m_center = box_trafo.translation();
m_orient_matrix = Geometry::translation_transform(m_center);
if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
if (!wxGetApp().obj_manipul()->is_world_coordinates() || m_force_local_coordinate) {
const GLVolume& v = *selection.get_first_volume();
m_orient_matrix = m_orient_matrix * v.get_instance_transformation().get_rotation_matrix();
if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates() || m_force_local_coordinate)
m_orient_matrix = m_orient_matrix * v.get_volume_transformation().get_rotation_matrix();
}

View file

@ -740,6 +740,7 @@ void GLGizmosManager::render_arrow(const GLCanvas3D& parent, EType highlighted_t
const float icons_size_x = 2.0f * m_layout.scaled_icons_size() * inv_cnv_w;
const float icons_size_y = 2.0f * m_layout.scaled_icons_size() * inv_cnv_h;
const float stride_y = 2.0f * m_layout.scaled_stride_y() * inv_cnv_h;
top_y -= stride_y;
for (size_t idx : selectable_idxs) {
if (idx == highlighted_type) {

View file

@ -118,8 +118,6 @@ void RotoptimizeJob::finalize(bool canceled, std::exception_ptr &eptr)
// Correct the z offset of the object which was corrupted be
// the rotation
o->ensure_on_bed();
// m_plater->find_new_position(o->instances);
}
if (!canceled)

View file

@ -120,7 +120,9 @@ enum class NotificationType
// Short meesage to fill space between start and finish of export
ExportOngoing,
// Progressbar of download from prusaslicer:// url
URLDownload
URLDownload,
// MacOS specific - PS comes forward even when downloader is not allowed
URLNotRegistered,
};
class NotificationManager
@ -916,6 +918,16 @@ private:
{NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10,
_u8L("Undo desktop integration failed.") },
{NotificationType::ExportOngoing, NotificationLevel::RegularNotificationLevel, 0, _u8L("Exporting.") },
{NotificationType::URLNotRegistered
, NotificationLevel::RegularNotificationLevel
, 10
, _u8L("PrusaSlicer recieved a download request from Printables.com, but it's not allowed. You can allow it")
, _u8L("here.")
, [](wxEvtHandler* evnthndlr) {
wxGetApp().open_preferences("downloader_url_registered", "Other");
return true;
} },
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) {
// wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }},
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") },

View file

@ -1038,25 +1038,50 @@ void ogStaticText::SetText(const wxString& value, bool wrap/* = true*/)
void ogStaticText::SetPathEnd(const std::string& link)
{
#ifndef __linux__
Bind(wxEVT_ENTER_WINDOW, [this, link](wxMouseEvent& event) {
SetToolTip(OptionsGroup::get_url(get_app_config()->get("suppress_hyperlinks") != "1" ? link : std::string()));
FocusText(true);
event.Skip();
});
Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& event) { FocusText(false); event.Skip(); });
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& event) {
if (HasCapture())
return;
this->CaptureMouse();
event.Skip();
} );
});
Bind(wxEVT_LEFT_UP, [link, this](wxMouseEvent& event) {
if (!HasCapture())
return;
ReleaseMouse();
OptionsGroup::launch_browser(link);
event.Skip();
} );
Bind(wxEVT_ENTER_WINDOW, [this, link](wxMouseEvent& event) {
SetToolTip(OptionsGroup::get_url(!get_app_config()->get_bool("suppress_hyperlinks") ? link : std::string()));
FocusText(true);
event.Skip();
});
Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& event) { FocusText(false); event.Skip(); });
#else
// Workaround: On Linux wxStaticText doesn't receive wxEVT_ENTER(LEAVE)_WINDOW events,
// so implement this behaviour trough wxEVT_MOTION events for this control and it's parent
Bind(wxEVT_MOTION, [link, this](wxMouseEvent& event) {
SetToolTip(OptionsGroup::get_url(!get_app_config()->get_bool("suppress_hyperlinks") ? link : std::string()));
FocusText(true);
event.Skip();
});
GetParent()->Bind(wxEVT_MOTION, [this](wxMouseEvent& event) {
FocusText(false);
event.Skip();
});
// On Linux a mouse capturing causes a totally application freeze
Bind(wxEVT_LEFT_UP, [link, this](wxMouseEvent& event) {
OptionsGroup::launch_browser(link);
event.Skip();
});
#endif
}
void ogStaticText::FocusText(bool focus)
@ -1065,7 +1090,10 @@ void ogStaticText::FocusText(bool focus)
return;
SetFont(focus ? Slic3r::GUI::wxGetApp().link_font() :
Slic3r::GUI::wxGetApp().normal_font());
Slic3r::GUI::wxGetApp().normal_font());
#ifdef __linux__
this->GetContainingSizer()->Layout();
#endif
Refresh();
}

View file

@ -462,7 +462,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
choice->set_selection();
}
update();
update(true);
}
void PhysicalPrinterDialog::update_printhost_buttons()
@ -632,11 +632,12 @@ void PhysicalPrinterDialog::update_host_type(bool printer_change)
Choice* choice = dynamic_cast<Choice*>(ht);
choice->set_values(types);
int index_in_choice = (printer_change ? 0 : last_in_conf);
int dif = (int)ht->m_opt.enum_def->values().size() - (int)types.size();
int index_in_choice = (printer_change ? std::clamp(last_in_conf - ((int)ht->m_opt.enum_def->values().size() - (int)types.size()), 0, (int)ht->m_opt.enum_def->values().size() - 1) : last_in_conf);
choice->set_value(index_in_choice);
if (link.supported && link.label == _(ht->m_opt.enum_def->label(index_in_choice)))
m_config->set_key_value("host_type", new ConfigOptionEnum<PrintHostType>(htPrusaLink));
else if (link.supported && link.label == _(ht->m_opt.enum_def->label(index_in_choice)))
else if (connect.supported && connect.label == _(ht->m_opt.enum_def->label(index_in_choice)))
m_config->set_key_value("host_type", new ConfigOptionEnum<PrintHostType>(htPrusaConnect));
else {
int host_type = std::clamp(index_in_choice + ((int)ht->m_opt.enum_def->values().size() - (int)types.size()), 0, (int)ht->m_opt.enum_def->values().size() - 1);

View file

@ -3116,38 +3116,6 @@ void Plater::priv::mirror(Axis axis)
view3D->mirror_selection(axis);
}
void Plater::find_new_position(const ModelInstancePtrs &instances)
{
arrangement::ArrangePolygons movable, fixed;
arrangement::ArrangeParams arr_params = get_arrange_params(this);
for (const ModelObject *mo : p->model.objects)
for (ModelInstance *inst : mo->instances) {
auto it = std::find(instances.begin(), instances.end(), inst);
auto arrpoly = get_arrange_poly(inst, this);
if (it == instances.end())
fixed.emplace_back(std::move(arrpoly));
else {
arrpoly.setter = [it](const arrangement::ArrangePolygon &p) {
if (p.is_arranged() && p.bed_idx == 0) {
Vec2d t = p.translation.cast<double>();
(*it)->apply_arrange_result(t, p.rotation);
}
};
movable.emplace_back(std::move(arrpoly));
}
}
if (auto wt = get_wipe_tower_arrangepoly(*this))
fixed.emplace_back(*wt);
arrangement::arrange(movable, fixed, this->build_volume().polygon(), arr_params);
for (auto & m : movable)
m.apply();
}
void Plater::priv::split_object()
{
int obj_idx = get_selected_object_idx();

View file

@ -330,7 +330,6 @@ public:
GLCanvas3D* get_current_canvas3D();
void arrange();
void find_new_position(const ModelInstancePtrs &instances);
void set_current_canvas_as_dirty();
void unbind_canvas_event_handlers();

View file

@ -10,7 +10,7 @@
#include "ButtonsDescription.hpp"
#include "OG_CustomCtrl.hpp"
#include "GLCanvas3D.hpp"
#include "ConfigWizard_private.hpp"
#include "ConfigWizard.hpp"
#include <boost/dll/runtime_symbol_info.hpp>
@ -712,7 +712,7 @@ void PreferencesDialog::accept(wxEvent&)
return;
#ifdef __linux__
if( downloader->get_perform_registration_linux())
DesktopIntegrationDialog::perform_desktop_integration(true);
DesktopIntegrationDialog::perform_downloader_desktop_integration();
#endif // __linux__
}

View file

@ -59,7 +59,7 @@ class PreferencesDialog : public DPIDialog
wxColourPickerCtrl* m_mode_advanced { nullptr };
wxColourPickerCtrl* m_mode_expert { nullptr };
DownloaderUtils::Worker* downloader{ nullptr };
DownloaderUtils::Worker* downloader { nullptr };
wxBookCtrlBase* tabs {nullptr};

View file

@ -16,6 +16,7 @@
#include "Tab.hpp"
#define FTS_FUZZY_MATCH_IMPLEMENTATION
#include "ExtraRenderers.hpp"
#include "fts_fuzzy_match.h"
#include "imgui/imconfig.h"
@ -500,6 +501,10 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher)
search_list_model = new SearchListModel(this);
search_list->AssociateModel(search_list_model);
#ifdef __WXMSW__
search_list->AppendColumn(new wxDataViewColumn("", new BitmapTextRenderer(true, wxDATAVIEW_CELL_INERT), SearchListModel::colIconMarkedText, wxCOL_WIDTH_AUTOSIZE, wxALIGN_LEFT));
search_list->GetColumn(SearchListModel::colIconMarkedText)->SetWidth(48 * em_unit());
#else
search_list->AppendBitmapColumn("", SearchListModel::colIcon);
wxDataViewTextRenderer* const markupRenderer = new wxDataViewTextRenderer();
@ -512,6 +517,7 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher)
search_list->GetColumn(SearchListModel::colIcon )->SetWidth(3 * em_unit());
search_list->GetColumn(SearchListModel::colMarkedText)->SetWidth(40 * em_unit());
#endif
wxBoxSizer* check_sizer = new wxBoxSizer(wxHORIZONTAL);
@ -725,10 +731,12 @@ void SearchDialog::OnLeftDown(wxMouseEvent& event)
void SearchDialog::msw_rescale()
{
const int& em = em_unit();
#ifdef __WXMSW__
search_list->GetColumn(SearchListModel::colIconMarkedText)->SetWidth(48 * em);
#else
search_list->GetColumn(SearchListModel::colIcon )->SetWidth(3 * em);
search_list->GetColumn(SearchListModel::colMarkedText)->SetWidth(45 * em);
#endif
const wxSize& size = wxSize(40 * em, 30 * em);
SetMinSize(size);
@ -787,8 +795,13 @@ void SearchListModel::sys_color_changed()
wxString SearchListModel::GetColumnType(unsigned int col) const
{
#ifdef __WXMSW__
if (col == colIconMarkedText)
return "DataViewBitmapText";
#else
if (col == colIcon)
return "wxBitmap";
#endif
return "string";
}
@ -797,12 +810,20 @@ void SearchListModel::GetValueByRow(wxVariant& variant,
{
switch (col)
{
#ifdef __WXMSW__
case colIconMarkedText: {
const ScalableBitmap& icon = m_icon[m_values[row].second];
variant << DataViewBitmapText(m_values[row].first, icon.bmp().GetBitmapFor(icon.parent()));
break;
}
#else
case colIcon:
variant << m_icon[m_values[row].second].bmp().GetBitmapFor(m_icon[m_values[row].second].parent());
break;
case colMarkedText:
variant = m_values[row].first;
break;
#endif
case colMax:
wxFAIL_MSG("invalid column");
default:

View file

@ -202,8 +202,12 @@ class SearchListModel : public wxDataViewVirtualListModel
public:
enum {
#ifdef __WXMSW__
colIconMarkedText,
#else
colIcon,
colMarkedText,
#endif
colMax
};

View file

@ -1571,7 +1571,10 @@ void TabPrint::build()
optgroup->append_single_option_line("first_layer_speed_over_raft");
optgroup = page->new_optgroup(L("Acceleration control (advanced)"));
optgroup->append_single_option_line("external_perimeter_acceleration");
optgroup->append_single_option_line("perimeter_acceleration");
optgroup->append_single_option_line("top_solid_infill_acceleration");
optgroup->append_single_option_line("solid_infill_acceleration");
optgroup->append_single_option_line("infill_acceleration");
optgroup->append_single_option_line("bridge_acceleration");
optgroup->append_single_option_line("first_layer_acceleration");
@ -1638,9 +1641,6 @@ void TabPrint::build()
optgroup->append_single_option_line("xy_size_compensation");
optgroup->append_single_option_line("elefant_foot_compensation", "elephant-foot-compensation_114487");
optgroup = page->new_optgroup(L("Other"));
optgroup->append_single_option_line("clip_multipart_objects");
optgroup = page->new_optgroup(L("Arachne perimeter generator"));
optgroup->append_single_option_line("wall_transition_angle");
optgroup->append_single_option_line("wall_transition_filter_deviation");
@ -1729,9 +1729,7 @@ void TabPrint::update_description_lines()
if (m_post_process_explanation) {
m_post_process_explanation->SetText(
_L("Post processing scripts shall modify G-code file in place."));
#ifndef __linux__
m_post_process_explanation->SetPathEnd("post-processing-scripts_283913");
#endif // __linux__
}
// upadte G-code substitutions from the current configuration
{

View file

@ -734,11 +734,14 @@ bool PrusaLink::get_storage(wxArrayString& output) const
const auto path = section.second.get_optional<std::string>("path");
const auto space = section.second.get_optional<std::string>("free_space");
const auto read_only = section.second.get_optional<bool>("read_only");
const auto ro = section.second.get_optional<bool>("ro"); // In PrusaLink 0.7.0RC2 "read_only" value is stored under "ro".
const auto available = section.second.get_optional<bool>("available");
if (path && (!available || *available)) {
StorageInfo si;
si.name = boost::nowide::widen(*path);
si.read_only = read_only ? *read_only : false; // If read_only is missing, assume it is NOT read only.
// If read_only is missing, assume it is NOT read only.
// si.read_only = read_only ? *read_only : false; // version without "ro"
si.read_only = (read_only ? *read_only : (ro ? *ro : false));
si.free_space = space ? std::stoll(*space) : 1; // If free_space is missing, assume there is free space.
storage.emplace_back(std::move(si));
}

View file

@ -3,7 +3,7 @@
set(SLIC3R_APP_NAME "PrusaSlicer")
set(SLIC3R_APP_KEY "PrusaSlicer")
set(SLIC3R_VERSION "2.6.0-alpha3")
set(SLIC3R_VERSION "2.6.0-alpha4")
set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN")
set(SLIC3R_RC_VERSION "2,6,0,0")
set(SLIC3R_RC_VERSION_DOTS "2.6.0.0")