Add the full source of BambuStudio

using version 1.0.10
This commit is contained in:
lane.wei 2022-07-15 23:37:19 +08:00 committed by Lane.Wei
parent 30bcadab3e
commit 1555904bef
3771 changed files with 1251328 additions and 0 deletions

View file

@ -0,0 +1,772 @@
#include "ArrangeJob.hpp"
#include "libslic3r/BuildVolume.hpp"
#include "libslic3r/SVG.hpp"
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/ModelArrange.hpp"
#include "slic3r/GUI/PartPlate.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "libnest2d/common.hpp"
#define SAVE_ARRANGE_POLY 0
namespace Slic3r { namespace GUI {
using ArrangePolygon = arrangement::ArrangePolygon;
// Cache the wti info
class WipeTower: public GLCanvas3D::WipeTowerInfo {
public:
explicit WipeTower(const GLCanvas3D::WipeTowerInfo &wti)
: GLCanvas3D::WipeTowerInfo(wti)
{}
explicit WipeTower(GLCanvas3D::WipeTowerInfo &&wti)
: GLCanvas3D::WipeTowerInfo(std::move(wti))
{}
void apply_arrange_result(const Vec2d& tr, double rotation, int item_id)
{
m_pos = unscaled(tr); m_rotation = rotation;
apply_wipe_tower();
}
ArrangePolygon get_arrange_polygon() const
{
Polygon ap({
{scaled(m_bb.min)},
{scaled(m_bb.max.x()), scaled(m_bb.min.y())},
{scaled(m_bb.max)},
{scaled(m_bb.min.x()), scaled(m_bb.max.y())}
});
ArrangePolygon ret;
ret.poly.contour = std::move(ap);
ret.translation = scaled(m_pos);
ret.rotation = m_rotation;
//BBS
ret.name = "WipeTower";
ret.is_virt_object = true;
ret.is_wipe_tower = true;
++ret.priority;
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " arrange: wipe tower info:" << m_bb << ", m_pos: " << m_pos.transpose();
return ret;
}
};
// BBS: add partplate logic
static WipeTower get_wipe_tower(const Plater &plater, int plate_idx)
{
return WipeTower{plater.canvas3D()->get_wipe_tower_info(plate_idx)};
}
arrangement::ArrangePolygon get_wipetower_arrange_poly(WipeTower* tower)
{
ArrangePolygon ap = tower->get_arrange_polygon();
ap.bed_idx = 0;
ap.setter = NULL; // do not move wipe tower
return ap;
}
void ArrangeJob::clear_input()
{
const Model &model = m_plater->model();
size_t count = 0, cunprint = 0; // To know how much space to reserve
for (auto obj : model.objects)
for (auto mi : obj->instances)
mi->printable ? count++ : cunprint++;
params.nonprefered_regions.clear();
m_selected.clear();
m_unselected.clear();
m_unprintable.clear();
m_locked.clear();
m_unarranged.clear();
m_selected.reserve(count + 1 /* for optional wti */);
m_unselected.reserve(count + 1 /* for optional wti */);
m_unprintable.reserve(cunprint /* for optional wti */);
m_locked.reserve(count + 1 /* for optional wti */);
current_plate_index = 0;
}
ArrangePolygon ArrangeJob::prepare_arrange_polygon(void* model_instance)
{
ModelInstance* instance = (ModelInstance*)model_instance;
const Slic3r::DynamicPrintConfig& config = wxGetApp().preset_bundle->full_config();
return get_instance_arrange_poly(instance, config);
}
void ArrangeJob::prepare_selected() {
PartPlateList& plate_list = m_plater->get_partplate_list();
clear_input();
Model& model = m_plater->model();
bool selected_is_locked = false;
//BBS: remove logic for unselected object
//double stride = bed_stride_x(m_plater);
std::vector<const Selection::InstanceIdxsList*>
obj_sel(model.objects.size(), nullptr);
for (auto& s : m_plater->get_selection().get_content())
if (s.first < int(obj_sel.size()))
obj_sel[size_t(s.first)] = &s.second;
// Go through the objects and check if inside the selection
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
const Selection::InstanceIdxsList* instlist = obj_sel[oidx];
ModelObject* mo = model.objects[oidx];
std::vector<bool> inst_sel(mo->instances.size(), false);
if (instlist)
for (auto inst_id : *instlist)
inst_sel[size_t(inst_id)] = true;
for (size_t i = 0; i < inst_sel.size(); ++i) {
ModelInstance* mi = mo->instances[i];
ArrangePolygon&& ap = prepare_arrange_polygon(mo->instances[i]);
//BBS: partplate_list preprocess
//remove the locked plate's instances, neither in selected, nor in un-selected
bool locked = plate_list.preprocess_arrange_polygon(oidx, i, ap, inst_sel[i]);
if (!locked)
{
ArrangePolygons& cont = mo->instances[i]->printable ?
(inst_sel[i] ? m_selected :
m_unselected) :
m_unprintable;
ap.itemid = cont.size();
cont.emplace_back(std::move(ap));
}
else
{
//skip this object due to be locked in plate
ap.itemid = m_locked.size();
m_locked.emplace_back(std::move(ap));
if (inst_sel[i])
selected_is_locked = true;
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%") % oidx % i;
}
}
}
// If the selection was empty arrange everything
//if (m_selected.empty()) m_selected.swap(m_unselected);
if (m_selected.empty()) {
if (!selected_is_locked)
m_selected.swap(m_unselected);
else {
m_plater->get_notification_manager()->push_notification(NotificationType::BBLPlateInfo,
NotificationManager::NotificationLevel::WarningNotificationLevel, into_u8(_L("All the selected objects are on the locked plate,\nWe can not do auto-arrange on these objects.")));
}
}
prepare_wipe_tower();
// The strides have to be removed from the fixed items. For the
// arrangeable (selected) items bed_idx is ignored and the
// translation is irrelevant.
//BBS: remove logic for unselected object
//for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride;
}
void ArrangeJob::prepare_all() {
PartPlateList& plate_list = m_plater->get_partplate_list();
clear_input();
Model &model = m_plater->model();
bool selected_is_locked = false;
// Go through the objects and check if inside the selection
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
ModelObject *mo = model.objects[oidx];
for (size_t i = 0; i < mo->instances.size(); ++i) {
ModelInstance * mi = mo->instances[i];
ArrangePolygon&& ap = prepare_arrange_polygon(mo->instances[i]);
//BBS: partplate_list preprocess
//remove the locked plate's instances, neither in selected, nor in un-selected
bool locked = plate_list.preprocess_arrange_polygon(oidx, i, ap, true);
if (!locked)
{
ArrangePolygons& cont = mo->instances[i]->printable ? m_selected :m_unprintable;
ap.itemid = cont.size();
cont.emplace_back(std::move(ap));
}
else
{
//skip this object due to be locked in plate
ap.itemid = m_locked.size();
m_locked.emplace_back(std::move(ap));
selected_is_locked = true;
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%") % oidx % i;
}
}
}
// If the selection was empty arrange everything
//if (m_selected.empty()) m_selected.swap(m_unselected);
if (m_selected.empty()) {
if (!selected_is_locked) {
m_plater->get_notification_manager()->push_notification(NotificationType::BBLPlateInfo,
NotificationManager::NotificationLevel::WarningNotificationLevel, into_u8(_L("No arrangable objects are selected.")));
}
else {
m_plater->get_notification_manager()->push_notification(NotificationType::BBLPlateInfo,
NotificationManager::NotificationLevel::WarningNotificationLevel, into_u8(_L("All the selected objects are on the locked plate,\nWe can not do auto-arrange on these objects.")));
}
}
prepare_wipe_tower();
}
void ArrangeJob::prepare_wipe_tower()
{
bool need_wipe_tower = false;
// estimate if we need wipe tower for all plates:
// if multile extruders have same bed temp, we need wipe tower
if (!params.is_seq_print) {
// need wipe tower if some object has multiple extruders (has paint-on colors)
if (!params.allow_multi_materials_on_same_plate) {
for (const auto &item : m_selected)
if (item.extrude_ids.size() > 1) {
need_wipe_tower = true;
break;
}
} else {
std::map<int, std::set<int>> bedTemp2extruderIds;
for (const auto &item : m_selected)
for (auto id : item.extrude_ids) { bedTemp2extruderIds[item.bed_temp].insert(id); }
for (const auto &be : bedTemp2extruderIds) {
if (be.second.size() > 1) {
need_wipe_tower = true;
break;
}
}
}
}
if (need_wipe_tower) {
// BBS: prepare wipe tower for all possible plates
ArrangePolygon wipe_tower_ap;
std::vector<bool> plates_have_wipe_tower(MAX_NUM_PLATES, false);
for (int bedid = 0; bedid < MAX_NUM_PLATES; bedid++)
if (auto wti = get_wipe_tower(*m_plater, bedid)) {
ArrangePolygon &&ap = get_wipetower_arrange_poly(&wti);
wipe_tower_ap = ap;
ap.bed_idx = bedid;
m_unselected.emplace_back(std::move(ap));
plates_have_wipe_tower[bedid] = true;
}
// if wipe tower is not init yet (no wipe tower in any plate before arrangement)
if (wipe_tower_ap.poly.empty()) {
auto &print = wxGetApp().plater()->get_partplate_list().get_current_fff_print();
wipe_tower_ap.poly.contour.points = print.first_layer_wipe_tower_corners(false);
wipe_tower_ap.name = "WipeTower";
wipe_tower_ap.is_virt_object = true;
wipe_tower_ap.is_wipe_tower = true;
}
for (int bedid = 0; bedid < MAX_NUM_PLATES; bedid++) {
if (!plates_have_wipe_tower[bedid]) {
wipe_tower_ap.bed_idx = bedid;
m_unselected.emplace_back(wipe_tower_ap);
}
}
}
}
arrangement::ArrangePolygon ArrangeJob::get_arrange_poly_(ModelInstance *mi)
{
arrangement::ArrangePolygon ap = get_arrange_poly(mi);
auto setter = ap.setter;
ap.setter = [this, setter, mi](const arrangement::ArrangePolygon &set_ap) {
setter(set_ap);
if (!set_ap.is_arranged())
m_unarranged.emplace_back(mi);
};
return ap;
}
//BBS: prepare current part plate for arranging
void ArrangeJob::prepare_partplate() {
clear_input();
PartPlateList& plate_list = m_plater->get_partplate_list();
PartPlate* plate = plate_list.get_curr_plate();
current_plate_index = plate_list.get_curr_plate_index();
assert(plate != nullptr);
if (plate->empty())
{
//no instances on this plate
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": no instances in current plate!");
return;
}
if (plate->is_locked()) {
m_plater->get_notification_manager()->push_notification(NotificationType::BBLPlateInfo,
NotificationManager::NotificationLevel::WarningNotificationLevel, into_u8(_L("This plate is locked,\nWe can not do auto-arrange on this plate.")));
return;
}
Model& model = m_plater->model();
// Go through the objects and check if inside the selection
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx)
{
ModelObject* mo = model.objects[oidx];
for (size_t inst_idx = 0; inst_idx < mo->instances.size(); ++inst_idx)
{
bool in_plate = plate->contain_instance(oidx, inst_idx);
ArrangePolygon&& ap = prepare_arrange_polygon(mo->instances[inst_idx]);
ArrangePolygons& cont = mo->instances[inst_idx]->printable ?
(in_plate ? m_selected : m_unselected) :
m_unprintable;
ap.itemid = cont.size();
bool locked = plate_list.preprocess_arrange_polygon_other_locked(oidx, inst_idx, ap, in_plate);
if (!locked)
{
cont.emplace_back(std::move(ap));
}
else
{
//skip this object due to be not in current plate, treated as locked
m_locked.emplace_back(std::move(ap));
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%") % oidx % inst_idx;
}
}
}
// BBS
if (auto wti = get_wipe_tower(*m_plater, current_plate_index)) {
ArrangePolygon&& ap = get_wipetower_arrange_poly(&wti);
m_unselected.emplace_back(std::move(ap));
}
// The strides have to be removed from the fixed items. For the
// arrangeable (selected) items bed_idx is ignored and the
// translation is irrelevant.
//BBS: remove logic for unselected object
//for (auto& p : m_unselected) p.translation(X) -= p.bed_idx * stride;
}
//BBS: add partplate logic
void ArrangeJob::prepare()
{
m_plater->get_notification_manager()->push_notification(NotificationType::ArrangeOngoing,
NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("Arranging..."));
m_plater->get_notification_manager()->bbl_close_plateinfo_notification();
{
const GLCanvas3D::ArrangeSettings &settings = static_cast<const GLCanvas3D *>(m_plater->canvas3D())->get_arrange_settings();
auto & print = wxGetApp().plater()->get_partplate_list().get_current_fff_print();
params.clearance_height_to_rod = print.config().extruder_clearance_height_to_rod.value;
params.clearance_height_to_lid = print.config().extruder_clearance_height_to_lid.value;
params.cleareance_radius = print.config().extruder_clearance_radius.value;
params.allow_rotations = settings.enable_rotation;
params.allow_multi_materials_on_same_plate = settings.allow_multi_materials_on_same_plate;
params.avoid_extrusion_cali_region = settings.avoid_extrusion_cali_region;
params.is_seq_print = settings.is_seq_print;
params.min_obj_distance = scaled(settings.distance);
}
//BBS update extruder params and speed table before arranging
Plater::setExtruderParams(Model::extruderParamsMap);
Plater::setPrintSpeedTable(Model::printSpeedMap);
int state = m_plater->get_prepare_state();
if (state == Job::JobPrepareState::PREPARE_STATE_DEFAULT) {
only_on_partplate = false;
prepare_selected();
}
else if (state == Job::JobPrepareState::PREPARE_STATE_MENU) {
only_on_partplate = true; // only arrange items on current plate
prepare_partplate();
}
//add the virtual object into unselect list if has
m_plater->get_partplate_list().preprocess_exclude_areas(m_unselected, MAX_NUM_PLATES);
#if SAVE_ARRANGE_POLY
if (1)
{ // subtract excluded region and get a polygon bed
auto& print = wxGetApp().plater()->get_partplate_list().get_current_fff_print();
auto print_config = print.config();
bed_poly.points = get_bed_shape(*m_plater->config());
Pointfs excluse_area_points = print_config.bed_exclude_area.values;
Polygons exclude_polys;
Polygon exclude_poly;
for (int i = 0; i < excluse_area_points.size(); i++) {
auto pt = excluse_area_points[i];
exclude_poly.points.emplace_back(scale_(pt.x()), scale_(pt.y()));
if (i % 4 == 3) { // exclude areas are always rectangle
exclude_polys.push_back(exclude_poly);
exclude_poly.points.clear();
}
}
bed_poly = diff({ bed_poly }, exclude_polys)[0];
}
BoundingBox bbox = bed_poly.bounding_box();
Point center = bbox.center();
auto polys_to_draw = m_selected;
for (auto it = polys_to_draw.begin(); it != polys_to_draw.end(); it++) {
it->poly.translate(center);
bbox.merge(it->poly);
}
SVG svg("SVG/arrange_poly.svg", bbox);
if (svg.is_opened()) {
svg.draw_outline(bed_poly);
//svg.draw_grid(bbox, "gray", scale_(0.05));
std::vector<std::string> color_array = { "red","black","yellow","gree","blue" };
for (auto it = polys_to_draw.begin(); it != polys_to_draw.end(); it++) {
std::string color = color_array[(it - polys_to_draw.begin()) % color_array.size()];
svg.add_comment(it->name);
svg.draw_text(get_extents(it->poly).min, it->name.c_str(), color.c_str());
svg.draw_outline(it->poly, color);
}
}
#endif
check_unprintable();
}
void ArrangeJob::check_unprintable()
{
for (auto it = m_selected.begin(); it != m_selected.end();) {
if (it->poly.area() < 0.001)
{
#if SAVE_ARRANGE_POLY
SVG svg("SVG/arrange_unprintable_"+it->name+".svg", get_extents(it->poly));
if (svg.is_opened())
svg.draw_outline(it->poly);
#endif
m_unprintable.push_back(*it);
auto msg = (boost::format(
_utf8("Object %s has zero size and can't be arranged."))
% _utf8(it->name)).str();
m_plater->get_notification_manager()->push_notification(NotificationType::BBLPlateInfo,
NotificationManager::NotificationLevel::WarningNotificationLevel, msg);
it = m_selected.erase(it);
}
else
it++;
}
}
void ArrangeJob::on_exception(const std::exception_ptr &eptr)
{
try {
if (eptr)
std::rethrow_exception(eptr);
} catch (libnest2d::GeometryException &) {
show_error(m_plater, _(L("Arrange failed. "
"Found some exceptions when processing object geometries.")));
} catch (std::exception &) {
PlaterJob::on_exception(eptr);
}
}
void ArrangeJob::process()
{
const GLCanvas3D::ArrangeSettings &settings =
static_cast<const GLCanvas3D*>(m_plater->canvas3D())->get_arrange_settings();
auto& print = wxGetApp().plater()->get_partplate_list().get_current_fff_print();
if (params.is_seq_print)
params.min_obj_distance = std::max(params.min_obj_distance, scaled(params.cleareance_radius));
if (params.avoid_extrusion_cali_region)
m_plater->get_partplate_list().preprocess_nonprefered_areas(m_unselected, MAX_NUM_PLATES);
double skirt_distance = print.has_skirt() ? print.config().skirt_distance.value : 0;
bool is_auto_brim = print.has_auto_brim();
double brim_max = 0;
if (is_auto_brim) {
brim_max = 0;
std::for_each(m_selected.begin(), m_selected.end(), [&](ArrangePolygon ap) { brim_max = std::max(brim_max, ap.auto_brim_width); });
}
else {
brim_max = print.has_brim() ? print.default_object_config().brim_width : 0;
std::for_each(m_selected.begin(), m_selected.end(), [&](ArrangePolygon ap) { brim_max = std::max(brim_max, ap.user_brim_width); });
}
// Note: skirt_distance is now defined between outermost brim and skirt, not the object and skirt.
// So we can't do max but do adding instead.
params.brim_skirt_distance = skirt_distance + brim_max;
params.bed_shrink_x = settings.bed_shrink_x + params.brim_skirt_distance;
params.bed_shrink_y = settings.bed_shrink_y + params.brim_skirt_distance;
// for sequential print, we need to inflate the bed because cleareance_radius is so large
if (params.is_seq_print) {
params.bed_shrink_x -= params.cleareance_radius/2;
params.bed_shrink_y -= params.cleareance_radius/2;
// dont forget to move the excluded region
for (auto& region : m_unselected) {
if (region.is_virt_object)
region.poly.translate(-scaled(params.cleareance_radius/2), -scaled(params.cleareance_radius/2));
}
}
// do not inflate brim_width. Objects are allowed to have overlapped brim.
std::for_each(m_selected.begin(), m_selected.end(), [&](auto& ap) {ap.inflation = params.min_obj_distance / 2; });
std::for_each(m_unselected.begin(), m_unselected.end(), [&](auto& ap) {ap.inflation = ap.is_virt_object ? scaled(params.brim_skirt_distance) : params.min_obj_distance / 2; });
m_plater->get_partplate_list().preprocess_exclude_areas(params.excluded_regions, 1);
// shrink bed by moving to center by dist
Points bedpts = get_bed_shape(*m_plater->config());
auto shrinkFun = [](Points& bedpts, double dist, int direction) {
#define SGN(x) ((x)>=0?1:-1)
Point center = Polygon(bedpts).bounding_box().center();
for (auto& pt : bedpts)
pt[direction] += dist * SGN(center[direction] - pt[direction]);
};
shrinkFun(bedpts, scaled(params.bed_shrink_x), 0);
shrinkFun(bedpts, scaled(params.bed_shrink_y), 1);
BOOST_LOG_TRIVIAL(debug) << "arrange bed_shrink_x=" << params.bed_shrink_x
<< ", brim_max= "<<brim_max<<", "
<< "; bedpts:" << bedpts[0].transpose() << ", " << bedpts[1].transpose() << ", " << bedpts[2].transpose() << ", " << bedpts[3].transpose();
params.stopcondition = [this]() { return was_canceled(); };
auto count = unsigned(m_selected.size());// + m_unprintable.size());
params.progressind = [this, count](unsigned num_finished, std::string str="") {
// if (num_finished >= 0 && num_finished <= count)
// update_status(int(float(num_finished) / count * 100), _L("Arranging") + " "+str);
};
if(!params.is_seq_print)
{
// force all heights be the same, so items are sorted by area
for (auto& ap : m_selected) ap.height = 1;
for (auto& ap : m_unselected) ap.height = 1;
}
{
BOOST_LOG_TRIVIAL(debug) << "items selected before arrange: ";
for (auto selected : m_selected)
BOOST_LOG_TRIVIAL(debug) << selected.name << ", extruder: " << selected.extrude_ids.back() << ", bed: " << selected.bed_idx
<< ", bed_temp: " << selected.first_bed_temp << ", print_temp: " << selected.print_temp;
BOOST_LOG_TRIVIAL(debug) << "items unselected before arrange: ";
for (auto item : m_unselected)
BOOST_LOG_TRIVIAL(debug) << item.name << ", extruder: " << item.extrude_ids.back() << ", bed: " << item.bed_idx << ", trans: " << item.translation.transpose();
}
arrangement::arrange(m_selected, m_unselected, bedpts, params);
// sort by item id
std::sort(m_selected.begin(), m_selected.end(), [](auto a, auto b) {return a.itemid < b.itemid; });
{
BOOST_LOG_TRIVIAL(debug) << "items selected after arrange: ";
for (auto selected : m_selected)
BOOST_LOG_TRIVIAL(debug) << selected.name << ", extruder: " << selected.extrude_ids.back() << ", bed: " << selected.bed_idx
<< ", bed_temp: " << selected.first_bed_temp << ", print_temp: " << selected.print_temp;
BOOST_LOG_TRIVIAL(debug) << "items unselected after arrange: ";
for (auto item : m_unselected)
if (!item.is_virt_object)
BOOST_LOG_TRIVIAL(debug) << item.name << ", extruder: " << item.extrude_ids.back() << ", bed: " << item.bed_idx
<< ", trans: " << item.translation.transpose();
}
arrangement::arrange(m_unprintable, {}, bedpts, params);
// put unpackable items to m_unprintable so they goes outside
bool we_have_unpackable_items = false;
for (auto item : m_selected) {
if (item.bed_idx < 0) {
//BBS: already processed in m_selected
//m_unprintable.push_back(std::move(item));
we_have_unpackable_items = true;
}
}
// finalize just here.
update_status(100,
was_canceled() ? _(L("Arranging canceled.")) :
we_have_unpackable_items ? _(L("Arranging is done but there are unpacked items. Reduce spacing and try again.")) : _(L("Arranging done.")));
}
static std::string concat_strings(const std::set<std::string> &strings,
const std::string &delim = "\n")
{
return std::accumulate(
strings.begin(), strings.end(), std::string(""),
[delim](const std::string &s, const std::string &name) {
return s + name + delim;
});
}
void ArrangeJob::finalize() {
// Ignore the arrange result if aborted.
if (was_canceled()) return;
// Unprintable items go to the last virtual bed
int beds = 0;
//BBS: partplate
PartPlateList& plate_list = m_plater->get_partplate_list();
//clear all the relations before apply the arrangement results
plate_list.clear();
//BBS: adjust the bed_index, create new plates, get the max bed_index
for (ArrangePolygon& ap : m_selected) {
//if (ap.bed_idx < 0) continue; // bed_idx<0 means unarrangable
//BBS: partplate postprocess
if (only_on_partplate)
plate_list.postprocess_bed_index_for_current_plate(ap);
else
plate_list.postprocess_bed_index_for_selected(ap);
beds = std::max(ap.bed_idx, beds);
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": arrange selected %4%: bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale<double>(ap.translation(X)) % unscale<double>(ap.translation(Y)) % ap.name;
}
//BBS: adjust the bed_index, create new plates, get the max bed_index
for (ArrangePolygon& ap : m_unselected)
{
if (ap.is_virt_object)
continue;
//BBS: partplate postprocess
if (!only_on_partplate)
plate_list.postprocess_bed_index_for_unselected(ap);
beds = std::max(ap.bed_idx, beds);
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":arrange unselected %4%: bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale<double>(ap.translation(X)) % unscale<double>(ap.translation(Y)) % ap.name;
}
for (ArrangePolygon& ap : m_locked) {
beds = std::max(ap.bed_idx, beds);
plate_list.postprocess_arrange_polygon(ap, false);
ap.apply();
}
// Apply the arrange result to all selected objects
for (ArrangePolygon& ap : m_selected) {
//BBS: partplate postprocess
plate_list.postprocess_arrange_polygon(ap, true);
ap.apply();
}
// Apply the arrange result to unselected objects(due to the sukodu-style column changes, the position of unselected may also be modified)
for (ArrangePolygon& ap : m_unselected)
{
if (ap.is_virt_object)
continue;
//BBS: partplate postprocess
plate_list.postprocess_arrange_polygon(ap, false);
ap.apply();
}
// Move the unprintable items to the last virtual bed.
// Note ap.apply() moves relatively according to bed_idx, so we need to subtract the orignal bed_idx
for (ArrangePolygon& ap : m_unprintable) {
ap.bed_idx = beds + 1;
plate_list.postprocess_arrange_polygon(ap, true);
ap.apply();
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":arrange m_unprintable: name: %4%, bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale<double>(ap.translation(X)) % unscale<double>(ap.translation(Y)) % ap.name;
}
m_plater->update();
// BBS
//wxGetApp().obj_manipul()->set_dirty();
if (!m_unarranged.empty()) {
std::set<std::string> names;
for (ModelInstance *mi : m_unarranged)
names.insert(mi->get_object()->name);
m_plater->get_notification_manager()->push_notification(GUI::format(
_L("Arrangement ignored the following objects which can't fit into a single bed:\n%s"),
concat_strings(names, "\n")));
}
m_plater->get_notification_manager()->close_notification_of_type(NotificationType::ArrangeOngoing);
//BBS: reload all objects due to arrange
plate_list.rebuild_plates_after_arrangement(!only_on_partplate);
// BBS: update slice context and gcode result.
m_plater->update_slicing_context_to_current_partplate();
wxGetApp().obj_list()->reload_all_plates();
m_plater->update();
Job::finalize();
}
std::optional<arrangement::ArrangePolygon>
get_wipe_tower_arrangepoly(const Plater &plater)
{
// BBS FIXME: use actual plate_idx
if (auto wti = get_wipe_tower(plater, 0))
return get_wipetower_arrange_poly(&wti);
return {};
}
//BBS: add sudoku-style stride
double bed_stride_x(const Plater* plater) {
double bedwidth = plater->build_volume().bounding_box().size().x();
return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
}
double bed_stride_y(const Plater* plater) {
double beddepth = plater->build_volume().bounding_box().size().y();
return scaled<double>((1. + LOGICAL_BED_GAP) * beddepth);
}
arrangement::ArrangeParams get_arrange_params(Plater *p)
{
const GLCanvas3D::ArrangeSettings &settings =
static_cast<const GLCanvas3D*>(p->canvas3D())->get_arrange_settings();
arrangement::ArrangeParams params;
params.allow_rotations = settings.enable_rotation;
params.min_obj_distance = scaled(settings.distance);
//BBS: add specific params
params.is_seq_print = settings.is_seq_print;
params.bed_shrink_x = settings.bed_shrink_x;
params.bed_shrink_y = settings.bed_shrink_y;
return params;
}
}} // namespace Slic3r::GUI

View file

@ -0,0 +1,84 @@
#ifndef ARRANGEJOB_HPP
#define ARRANGEJOB_HPP
#include "PlaterJob.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "libslic3r/Arrange.hpp"
#include "libslic3r/Model.hpp"
namespace Slic3r {
class ModelInstance;
namespace GUI {
class ArrangeJob : public PlaterJob
{
using ArrangePolygon = arrangement::ArrangePolygon;
using ArrangePolygons = arrangement::ArrangePolygons;
//BBS: add locked logic
ArrangePolygons m_selected, m_unselected, m_unprintable, m_locked;
std::vector<ModelInstance*> m_unarranged;
std::map<int, ArrangePolygons> m_selected_groups; // groups of selected items for sequential printing
arrangement::ArrangeParams params;
int current_plate_index = 0;
Polygon bed_poly;
// clear m_selected and m_unselected, reserve space for next usage
void clear_input();
// Prepare the selected and unselected items separately. If nothing is
// selected, behaves as if everything would be selected.
void prepare_selected();
void prepare_all();
//BBS:prepare the items from current selected partplate
void prepare_partplate();
void prepare_wipe_tower();
ArrangePolygon get_arrange_poly_(ModelInstance* mi);
ArrangePolygon prepare_arrange_polygon(void* instance);
protected:
void prepare() override;
void check_unprintable();
void on_exception(const std::exception_ptr &) override;
void process() override;
public:
ArrangeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
: PlaterJob{std::move(pri), plater}
{}
int status_range() const override
{
return int(m_selected.size() + m_unprintable.size());
}
void finalize() override;
};
std::optional<arrangement::ArrangePolygon> get_wipe_tower_arrangepoly(const Plater &);
// The gap between logical beds in the x axis expressed in ratio of
// the current bed width.
static const constexpr double LOGICAL_BED_GAP = 1. / 5.;
//BBS: add sudoku-style strides for x and y
// Stride between logical beds
double bed_stride_x(const Plater* plater);
double bed_stride_y(const Plater* plater);
arrangement::ArrangeParams get_arrange_params(Plater *p);
}} // namespace Slic3r::GUI
#endif // ARRANGEJOB_HPP

View file

@ -0,0 +1,149 @@
#include "BindJob.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
namespace Slic3r {
namespace GUI {
wxDEFINE_EVENT(EVT_BIND_UPDATE_MESSAGE, wxCommandEvent);
wxDEFINE_EVENT(EVT_BIND_MACHINE_SUCCESS, wxCommandEvent);
wxDEFINE_EVENT(EVT_BIND_MACHINE_FAIL, wxCommandEvent);
static wxString waiting_auth_str = _L("Logging in");
static wxString login_failed_str = _L("Login failed");
wxString get_login_fail_reason(std::string fail_reason)
{
if (fail_reason == "NO Regions")
return _L("The region parameter is incorrrect");
else if (fail_reason == "Cloud Timeout")
return _L("Failure of printer login");
else if (fail_reason == "Ticket Failed")
return _L("Failed to get ticket");
else if (fail_reason == "Wait Auth Timeout")
return _L("User authorization timeout");
else if (fail_reason == "Bind Failure")
return _L("Failure of bind");
else
return _L("Unknown Failure");
}
BindJob::BindJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater, std::string dev_id, std::string dev_ip)
: PlaterJob{std::move(pri), plater},
m_dev_id(dev_id),
m_dev_ip(dev_ip)
{
;
}
void BindJob::on_exception(const std::exception_ptr &eptr)
{
try {
if (eptr)
std::rethrow_exception(eptr);
} catch (std::exception &e) {
PlaterJob::on_exception(eptr);
}
}
void BindJob::on_success(std::function<void()> success)
{
m_success_fun = success;
}
void BindJob::update_status(int st, const wxString &msg)
{
GUI::Job::update_status(st, msg);
//post_event(wxCommandEvent(EVT_BIND_UPDATE_MESSAGE), msg);
wxCommandEvent event(EVT_BIND_UPDATE_MESSAGE);
event.SetString(msg);
event.SetEventObject(m_event_handle);
wxPostEvent(m_event_handle, event);
}
void BindJob::process()
{
/* display info */
wxString msg = waiting_auth_str;
int curr_percent = 0;
NetworkAgent* m_agent = wxGetApp().getAgent();
if (!m_agent) { return; }
// get timezone
wxDateTime::TimeZone tz(wxDateTime::Local);
long offset = tz.GetOffset();
std::string timezone = get_timezone_utc_hm(offset);
int result = m_agent->bind(m_dev_ip, timezone,
[this, &curr_percent, &msg](int stage, int code, std::string info) {
if (stage == BBL::BindJobStage::LoginStageConnect) {
curr_percent = 15;
msg = _L("Logging in");
} else if (stage == BBL::BindJobStage::LoginStageLogin) {
curr_percent = 30;
msg = _L("Logging in");
} else if (stage == BBL::BindJobStage::LoginStageWaitForLogin) {
curr_percent = 45;
msg = _L("Logging in");
} else if (stage == BBL::BindJobStage::LoginStageGetIdentify) {
curr_percent = 60;
msg = _L("Logging in");
} else if (stage == BBL::BindJobStage::LoginStageWaitAuth) {
curr_percent = 80;
msg = _L("Logging in");
} else if (stage == BBL::BindJobStage::LoginStageFinished) {
curr_percent = 100;
msg = _L("Logging in");
}
if (code != 0) {
msg = login_failed_str + wxString::Format("(code=%d,info=%s)", code, info);
}
update_status(curr_percent, msg);
}
);
if (result < 0) {
BOOST_LOG_TRIVIAL(trace) << "login: result = " << result;
post_fail_event();
return;
}
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) {
BOOST_LOG_TRIVIAL(trace) << "login: dev is null";
post_fail_event();
return;
}
dev->update_user_machine_list_info();
wxCommandEvent event(EVT_BIND_MACHINE_SUCCESS);
event.SetEventObject(m_event_handle);
wxPostEvent(m_event_handle, event);
return;
}
void BindJob::finalize()
{
if (was_canceled()) return;
Job::finalize();
}
void BindJob::set_event_handle(wxWindow *hanle)
{
m_event_handle = hanle;
}
void BindJob::post_fail_event()
{
wxCommandEvent event(EVT_BIND_MACHINE_FAIL);
event.SetEventObject(m_event_handle);
wxPostEvent(m_event_handle, event);
}
}} // namespace Slic3r::GUI

View file

@ -0,0 +1,47 @@
#ifndef __BindJob_HPP__
#define __BindJob_HPP__
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include "PlaterJob.hpp"
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
class BindJob : public PlaterJob
{
wxWindow * m_event_handle{nullptr};
std::function<void()> m_success_fun{nullptr};
std::string m_dev_id;
std::string m_dev_ip;
bool m_job_finished{ false };
int m_print_job_completed_id = 0;
protected:
void on_exception(const std::exception_ptr &) override;
public:
BindJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater, std::string dev_id, std::string dev_ip);
int status_range() const override
{
return 100;
}
bool is_finished() { return m_job_finished; }
void on_success(std::function<void()> success);
void update_status(int st, const wxString &msg);
void process() override;
void finalize() override;
void set_event_handle(wxWindow* hanle);
void post_fail_event();
};
wxDECLARE_EVENT(EVT_BIND_UPDATE_MESSAGE, wxCommandEvent);
wxDECLARE_EVENT(EVT_BIND_MACHINE_SUCCESS, wxCommandEvent);
wxDECLARE_EVENT(EVT_BIND_MACHINE_FAIL, wxCommandEvent);
}} // namespace Slic3r::GUI
#endif // ARRANGEJOB_HPP

View file

@ -0,0 +1,298 @@
#include "FillBedJob.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/ModelArrange.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include <numeric>
namespace Slic3r {
namespace GUI {
//BBS: add partplate related logic
void FillBedJob::prepare()
{
PartPlateList& plate_list = m_plater->get_partplate_list();
m_locked.clear();
m_selected.clear();
m_unselected.clear();
m_bedpts.clear();
m_object_idx = m_plater->get_selected_object_idx();
if (m_object_idx == -1)
return;
//select current plate at first
int sel_id = m_plater->get_selection().get_instance_idx();
sel_id = std::max(sel_id, 0);
int sel_ret = plate_list.select_plate_by_obj(m_object_idx, sel_id);
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":select plate obj_id %1%, ins_id %2%, ret %3%}") % m_object_idx % sel_id % sel_ret;
PartPlate* plate = plate_list.get_curr_plate();
Model& model = m_plater->model();
BoundingBox plate_bb = plate->get_bounding_box_crd();
int plate_cols = plate_list.get_plate_cols();
int cur_plate_index = plate->get_index();
ModelObject *model_object = m_plater->model().objects[m_object_idx];
if (model_object->instances.empty()) return;
m_selected.reserve(model_object->instances.size());
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx)
{
ModelObject* mo = model.objects[oidx];
for (size_t inst_idx = 0; inst_idx < mo->instances.size(); ++inst_idx)
{
bool selected = (oidx == m_object_idx);
ArrangePolygon ap = get_arrange_poly(mo->instances[inst_idx]);
BoundingBox ap_bb = ap.transformed_poly().contour.bounding_box();
ap.height = 1;
ap.name = mo->name;
if (selected)
{
if (mo->instances[inst_idx]->printable)
{
++ap.priority;
ap.itemid = m_selected.size();
m_selected.emplace_back(ap);
}
else
{
if (plate_bb.contains(ap_bb))
{
ap.bed_idx = 0;
ap.itemid = m_unselected.size();
ap.row = cur_plate_index / plate_cols;
ap.col = cur_plate_index % plate_cols;
ap.translation(X) -= bed_stride_x(m_plater) * ap.col;
ap.translation(Y) += bed_stride_y(m_plater) * ap.row;
m_unselected.emplace_back(ap);
}
else
{
ap.bed_idx = PartPlateList::MAX_PLATES_COUNT;
ap.itemid = m_locked.size();
m_locked.emplace_back(ap);
}
}
}
else
{
if (plate_bb.contains(ap_bb))
{
ap.bed_idx = 0;
ap.itemid = m_unselected.size();
ap.row = cur_plate_index / plate_cols;
ap.col = cur_plate_index % plate_cols;
ap.translation(X) -= bed_stride_x(m_plater) * ap.col;
ap.translation(Y) += bed_stride_y(m_plater) * ap.row;
m_unselected.emplace_back(ap);
}
else
{
ap.bed_idx = PartPlateList::MAX_PLATES_COUNT;
ap.itemid = m_locked.size();
m_locked.emplace_back(ap);
}
}
}
}
/*
for (ModelInstance *inst : model_object->instances)
if (inst->printable) {
ArrangePolygon ap = get_arrange_poly(inst);
// Existing objects need to be included in the result. Only
// the needed amount of object will be added, no more.
++ap.priority;
m_selected.emplace_back(ap);
}*/
if (m_selected.empty()) return;
//add the virtual object into unselect list if has
plate_list.preprocess_exclude_areas(m_unselected);
m_bedpts = get_bed_shape(*m_plater->config());
auto &objects = m_plater->model().objects;
/*BoundingBox bedbb = get_extents(m_bedpts);
for (size_t idx = 0; idx < objects.size(); ++idx)
if (int(idx) != m_object_idx)
for (ModelInstance *mi : objects[idx]->instances) {
ArrangePolygon ap = get_arrange_poly(mi);
auto ap_bb = ap.transformed_poly().contour.bounding_box();
if (ap.bed_idx == 0 && !bedbb.contains(ap_bb))
ap.bed_idx = arrangement::UNARRANGED;
m_unselected.emplace_back(ap);
}
if (auto wt = get_wipe_tower_arrangepoly(*m_plater))
m_unselected.emplace_back(std::move(*wt));*/
double sc = scaled<double>(1.) * scaled(1.);
ExPolygon poly = m_selected.front().poly;
double poly_area = poly.area() / sc;
double unsel_area = std::accumulate(m_unselected.begin(),
m_unselected.end(), 0.,
[cur_plate_index](double s, const auto &ap) {
//BBS: m_unselected instance is in the same partplate
return s + (ap.bed_idx == cur_plate_index) * ap.poly.area();
//return s + (ap.bed_idx == 0) * ap.poly.area();
}) / sc;
double fixed_area = unsel_area + m_selected.size() * poly_area;
double bed_area = Polygon{m_bedpts}.area() / sc;
// This is the maximum number of items, the real number will always be close but less.
int needed_items = (bed_area - fixed_area) / poly_area;
//int sel_id = m_plater->get_selection().get_instance_idx();
// if the selection is not a single instance, choose the first as template
//sel_id = std::max(sel_id, 0);
ModelInstance *mi = model_object->instances[sel_id];
ArrangePolygon template_ap = get_arrange_poly(mi);
for (int i = 0; i < needed_items; ++i) {
ArrangePolygon ap = template_ap;
ap.poly = m_selected.front().poly;
ap.bed_idx = PartPlateList::MAX_PLATES_COUNT;
ap.height = 1;
ap.itemid = -1;
ap.setter = [this, mi](const ArrangePolygon &p) {
ModelObject *mo = m_plater->model().objects[m_object_idx];
ModelInstance *inst = mo->add_instance(*mi);
inst->apply_arrange_result(p.translation.cast<double>(), p.rotation);
};
m_selected.emplace_back(ap);
}
m_status_range = m_selected.size();
// The strides have to be removed from the fixed items. For the
// arrangeable (selected) items bed_idx is ignored and the
// translation is irrelevant.
//BBS: remove logic for unselected object
/*double stride = bed_stride(m_plater);
for (auto &p : m_unselected)
if (p.bed_idx > 0)
p.translation(X) -= p.bed_idx * stride;*/
}
void FillBedJob::process()
{
if (m_object_idx == -1 || m_selected.empty()) return;
const GLCanvas3D::ArrangeSettings &settings =
static_cast<const GLCanvas3D*>(m_plater->canvas3D())->get_arrange_settings();
arrangement::ArrangeParams params;
params.allow_rotations = settings.enable_rotation;
params.min_obj_distance = scaled(settings.distance);
bool do_stop = false;
params.stopcondition = [this, &do_stop]() {
return was_canceled() || do_stop;
};
params.progressind = [this](unsigned st,std::string str="") {
// if (st > 0)
// update_status(int(m_status_range - st), _L("Filling bed " + str));
};
params.on_packed = [&do_stop] (const ArrangePolygon &ap) {
do_stop = ap.bed_idx > 0 && ap.priority == 0;
};
arrangement::arrange(m_selected, m_unselected, m_bedpts, params);
// finalize just here.
// update_status(m_status_range, was_canceled() ?
// _(L("Bed filling canceled.")) :
// _(L("Bed filling done.")));
}
void FillBedJob::finalize()
{
// Ignore the arrange result if aborted.
if (was_canceled()) return;
if (m_object_idx == -1) return;
ModelObject *model_object = m_plater->model().objects[m_object_idx];
if (model_object->instances.empty()) return;
//BBS: partplate
PartPlateList& plate_list = m_plater->get_partplate_list();
int plate_cols = plate_list.get_plate_cols();
int cur_plate = plate_list.get_curr_plate_index();
size_t inst_cnt = model_object->instances.size();
int added_cnt = std::accumulate(m_selected.begin(), m_selected.end(), 0, [](int s, auto &ap) {
return s + int(ap.priority == 0 && ap.bed_idx == 0);
});
if (added_cnt > 0) {
//BBS: adjust the selected instances
for (ArrangePolygon& ap : m_selected) {
if (ap.bed_idx != 0) {
if (ap.itemid == -1)
continue;
ap.bed_idx = plate_list.get_plate_count();
}
else
ap.bed_idx = cur_plate;
ap.row = ap.bed_idx / plate_cols;
ap.col = ap.bed_idx % plate_cols;
ap.translation(X) += bed_stride_x(m_plater) * ap.col;
ap.translation(Y) -= bed_stride_y(m_plater) * ap.row;
ap.apply();
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":selected: bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale<double>(ap.translation(X)) % unscale<double>(ap.translation(Y));
}
for (size_t inst_idx = 0; inst_idx < model_object->instances.size(); ++inst_idx)
{
plate_list.notify_instance_update(m_object_idx, inst_idx);
}
/*for (ArrangePolygon& ap : m_selected) {
if (ap.bed_idx != arrangement::UNARRANGED && (ap.priority != 0 || ap.bed_idx == 0))
ap.apply();
}*/
model_object->ensure_on_bed();
m_plater->update();
//BBS: add partplate related logic
int added_cnt = std::accumulate(m_selected.begin(), m_selected.end(), 0,
[cur_plate](int s, auto& ap) {
return s + int(ap.priority == 0 && ap.bed_idx == cur_plate);
//return s + int(ap.priority == 0 && ap.bed_idx == 0);
});
// FIXME: somebody explain why this is needed for increase_object_instances
if (inst_cnt == 1) added_cnt++;
if (added_cnt > 0)
m_plater->sidebar()
.obj_list()->increase_object_instances(m_object_idx, size_t(added_cnt));
}
Job::finalize();
}
}} // namespace Slic3r::GUI

View file

@ -0,0 +1,46 @@
#ifndef FILLBEDJOB_HPP
#define FILLBEDJOB_HPP
#include "ArrangeJob.hpp"
namespace Slic3r { namespace GUI {
class Plater;
class FillBedJob : public PlaterJob
{
int m_object_idx = -1;
using ArrangePolygon = arrangement::ArrangePolygon;
using ArrangePolygons = arrangement::ArrangePolygons;
ArrangePolygons m_selected;
ArrangePolygons m_unselected;
//BBS: add partplate related logic
ArrangePolygons m_locked;;
Points m_bedpts;
int m_status_range = 0;
protected:
void prepare() override;
void process() override;
public:
FillBedJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
: PlaterJob{std::move(pri), plater}
{}
int status_range() const override
{
return m_status_range;
}
void finalize() override;
};
}} // namespace Slic3r::GUI
#endif // FILLBEDJOB_HPP

157
src/slic3r/GUI/Jobs/Job.cpp Normal file
View file

@ -0,0 +1,157 @@
#include <algorithm>
#include <exception>
#include "Job.hpp"
#include <libslic3r/Thread.hpp>
#include <boost/log/trivial.hpp>
namespace Slic3r {
void GUI::Job::run(std::exception_ptr &eptr)
{
m_running.store(true);
try {
process();
} catch (...) {
eptr = std::current_exception();
}
m_running.store(false);
// ensure to call the last status to finalize the job
update_status(status_range(), "");
}
void GUI::Job::update_status(int st, const wxString &msg)
{
auto evt = new wxThreadEvent(wxEVT_THREAD, m_thread_evt_id);
evt->SetInt(st);
evt->SetString(msg);
wxQueueEvent(this, evt);
}
GUI::Job::Job(std::shared_ptr<ProgressIndicator> pri)
: m_progress(std::move(pri))
{
m_thread_evt_id = wxNewId();
Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
if (m_finalizing) return;
auto msg = evt.GetString();
if (!msg.empty() && !m_worker_error)
m_progress->set_status_text(msg.ToUTF8().data());
if (m_finalized) return;
m_progress->set_progress(evt.GetInt());
if (evt.GetInt() == status_range() || m_worker_error) {
// set back the original range and cancel callback
m_progress->set_range(m_range);
// Make sure progress indicators get the last value of their range
// to make sure they close, fade out, whathever
m_progress->set_progress(m_range);
m_progress->set_cancel_callback();
wxEndBusyCursor();
if (m_worker_error) {
m_finalized = true;
m_progress->set_status_text("");
m_progress->set_progress(m_range);
on_exception(m_worker_error);
}
else {
// This is an RAII solution to remember that finalization is
// running. The run method calls update_status(status_range(), "")
// at the end, which queues up a call to this handler in all cases.
// If process also calls update_status with maxed out status arg
// it will call this handler twice. It is not a problem unless
// yield is called inside the finilize() method, which would
// jump out of finalize and call this handler again.
struct Finalizing {
bool &flag;
Finalizing (bool &f): flag(f) { flag = true; }
~Finalizing() { flag = false; }
} fin(m_finalizing);
finalize();
}
// dont do finalization again for the same process
m_finalized = true;
}
}, m_thread_evt_id);
}
void GUI::Job::start()
{ // Start the job. No effect if the job is already running
if (!m_running.load()) {
prepare();
// Save the current status indicatior range and push the new one
m_range = m_progress->get_range();
m_progress->set_range(status_range());
// init cancellation flag and set the cancel callback
m_canceled.store(false);
m_progress->set_cancel_callback(
[this]() { m_canceled.store(true); });
m_finalized = false;
m_finalizing = false;
// Changing cursor to busy
wxBeginBusyCursor();
try { // Execute the job
m_worker_error = nullptr;
m_thread = create_thread([this] { this->run(m_worker_error); });
} catch (std::exception &) {
update_status(status_range(),
_(L("Error! Unable to create thread!")));
}
// The state changes will be undone when the process hits the
// last status value, in the status update handler (see ctor)
}
}
bool GUI::Job::join(int timeout_ms)
{
if (!m_thread.joinable()) return true;
if (timeout_ms <= 0)
m_thread.join();
else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
return false;
return true;
}
void GUI::ExclusiveJobGroup::start(size_t jid) {
assert(jid < m_jobs.size());
stop_all();
m_jobs[jid]->start();
}
void GUI::ExclusiveJobGroup::join_all(int wait_ms)
{
std::vector<bool> aborted(m_jobs.size(), false);
for (size_t jid = 0; jid < m_jobs.size(); ++jid)
aborted[jid] = m_jobs[jid]->join(wait_ms);
if (!std::all_of(aborted.begin(), aborted.end(), [](bool t) { return t; }))
BOOST_LOG_TRIVIAL(error) << "Could not abort a job!";
}
bool GUI::ExclusiveJobGroup::is_any_running() const
{
return std::any_of(m_jobs.begin(), m_jobs.end(),
[](const std::unique_ptr<GUI::Job> &j) {
return j->is_running();
});
}
}

128
src/slic3r/GUI/Jobs/Job.hpp Normal file
View file

@ -0,0 +1,128 @@
#ifndef JOB_HPP
#define JOB_HPP
#include <atomic>
#include <exception>
#include "libslic3r/libslic3r.h"
#include <slic3r/GUI/I18N.hpp>
#include "ProgressIndicator.hpp"
#include <wx/event.h>
#include <boost/thread.hpp>
namespace Slic3r { namespace GUI {
// A class to handle UI jobs like arranging and optimizing rotation.
// These are not instant jobs, the user has to be informed about their
// state in the status progress indicator. On the other hand they are
// separated from the background slicing process. Ideally, these jobs should
// run when the background process is not running.
//
// TODO: A mechanism would be useful for blocking the plater interactions:
// objects would be frozen for the user. In case of arrange, an animation
// could be shown, or with the optimize orientations, partial results
// could be displayed.
class Job : public wxEvtHandler
{
int m_range = 100;
int m_thread_evt_id = wxID_ANY;
boost::thread m_thread;
std::atomic<bool> m_running{false}, m_canceled{false};
bool m_finalized = false, m_finalizing = false;
std::shared_ptr<ProgressIndicator> m_progress;
std::exception_ptr m_worker_error = nullptr;
void run(std::exception_ptr &);
protected:
// status range for a particular job
virtual int status_range() const { return 100; }
// status update, to be used from the work thread (process() method)
void update_status(int st, const wxString &msg = "");
bool was_canceled() const { return m_canceled.load(); }
// Launched just before start(), a job can use it to prepare internals
virtual void prepare() {}
// The method where the actual work of the job should be defined.
virtual void process() = 0;
// Launched when the job is finished. It refreshes the 3Dscene by def.
virtual void finalize() { m_finalized = true; }
// Exceptions occuring in process() are redirected from the worker thread
// into the main (UI) thread. This method is called from the main thread and
// can be overriden to handle these exceptions.
virtual void on_exception(const std::exception_ptr &eptr)
{
if (eptr) std::rethrow_exception(eptr);
}
public:
enum JobPrepareState {
PREPARE_STATE_DEFAULT = 0,
PREPARE_STATE_MENU = 1,
};
Job(std::shared_ptr<ProgressIndicator> pri);
bool is_finalized() const { return m_finalized; }
Job(const Job &) = delete;
Job(Job &&) = delete;
Job &operator=(const Job &) = delete;
Job &operator=(Job &&) = delete;
void start();
// To wait for the running job and join the threads. False is
// returned if the timeout has been reached and the job is still
// running. Call cancel() before this fn if you want to explicitly
// end the job.
bool join(int timeout_ms = 0);
bool is_running() const { return m_running.load(); }
void cancel() { m_canceled.store(true); }
};
// Jobs defined inside the group class will be managed so that only one can
// run at a time. Also, the background process will be stopped if a job is
// started.
class ExclusiveJobGroup
{
static const int ABORT_WAIT_MAX_MS = 10000;
std::vector<std::unique_ptr<GUI::Job>> m_jobs;
protected:
virtual void before_start() {}
public:
virtual ~ExclusiveJobGroup() = default;
size_t add_job(std::unique_ptr<GUI::Job> &&job)
{
m_jobs.emplace_back(std::move(job));
return m_jobs.size() - 1;
}
void start(size_t jid);
void cancel_all() { for (auto& j : m_jobs) j->cancel(); }
void join_all(int wait_ms = 0);
void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); }
bool is_any_running() const;
};
}} // namespace Slic3r::GUI
#endif // JOB_HPP

View file

@ -0,0 +1,33 @@
#include "NotificationProgressIndicator.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
namespace Slic3r { namespace GUI {
NotificationProgressIndicator::NotificationProgressIndicator(NotificationManager *nm): m_nm{nm} {}
void NotificationProgressIndicator::set_range(int range)
{
m_nm->progress_indicator_set_range(range);
}
void NotificationProgressIndicator::set_cancel_callback(CancelFn fn)
{
m_nm->progress_indicator_set_cancel_callback(std::move(fn));
}
void NotificationProgressIndicator::set_progress(int pr)
{
m_nm->progress_indicator_set_progress(pr);
}
void NotificationProgressIndicator::set_status_text(const char *msg)
{
m_nm->progress_indicator_set_status_text(msg);
}
int NotificationProgressIndicator::get_range() const
{
return m_nm->progress_indicator_get_range();
}
}} // namespace Slic3r::GUI

View file

@ -0,0 +1,26 @@
#ifndef NOTIFICATIONPROGRESSINDICATOR_HPP
#define NOTIFICATIONPROGRESSINDICATOR_HPP
#include "ProgressIndicator.hpp"
namespace Slic3r { namespace GUI {
class NotificationManager;
class NotificationProgressIndicator: public ProgressIndicator {
NotificationManager *m_nm = nullptr;
public:
explicit NotificationProgressIndicator(NotificationManager *nm);
void set_range(int range) override;
void set_cancel_callback(CancelFn = CancelFn()) override;
void set_progress(int pr) override;
void set_status_text(const char *) override; // utf8 char array
int get_range() const override;
};
}} // namespace Slic3r::GUI
#endif // NOTIFICATIONPROGRESSINDICATOR_HPP

View file

@ -0,0 +1,242 @@
#include "OrientJob.hpp"
#include "libslic3r/Model.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "libslic3r/PresetBundle.hpp"
namespace Slic3r { namespace GUI {
void OrientJob::clear_input()
{
const Model &model = m_plater->model();
size_t count = 0, cunprint = 0; // To know how much space to reserve
for (auto obj : model.objects)
for (auto mi : obj->instances)
mi->printable ? count++ : cunprint++;
m_selected.clear();
m_unselected.clear();
m_unprintable.clear();
m_selected.reserve(count);
m_unselected.reserve(count);
m_unprintable.reserve(cunprint);
}
//BBS: add only one plate mode and lock logic
void OrientJob::prepare_selection(std::vector<bool> obj_sel, bool only_one_plate)
{
Model& model = m_plater->model();
PartPlateList& plate_list = m_plater->get_partplate_list();
//OrientMeshs selected_in_lock, unselect_in_lock;
bool selected_is_locked = false;
// Go through the objects and check if inside the selection
for (size_t oidx = 0; oidx < obj_sel.size(); ++oidx) {
bool selected = obj_sel[oidx];
ModelObject* mo = model.objects[oidx];
for (size_t inst_idx = 0; inst_idx < mo->instances.size(); ++inst_idx)
{
ModelInstance* mi = mo->instances[inst_idx];
OrientMesh&& om = get_orient_mesh(mi);
bool locked = false;
if (!only_one_plate) {
int plate_index = plate_list.find_instance(oidx, inst_idx);
if ((plate_index >= 0)&&(plate_index < plate_list.get_plate_count())) {
if (plate_list.is_locked(plate_index)) {
if (selected) {
//selected_in_lock.emplace_back(std::move(om));
selected_is_locked = true;
}
//else
// unselect_in_lock.emplace_back(std::move(om));
continue;
}
}
}
auto& cont = mo->printable ? (selected ? m_selected : m_unselected) : m_unprintable;
cont.emplace_back(std::move(om));
}
}
// If the selection was empty orient everything
if (m_selected.empty()) {
if (!selected_is_locked) {
m_selected.swap(m_unselected);
//m_unselected.insert(m_unselected.begin(), unselect_in_lock.begin(), unselect_in_lock.end());
}
else {
m_plater->get_notification_manager()->push_notification(NotificationType::BBLPlateInfo,
NotificationManager::NotificationLevel::WarningNotificationLevel, into_u8(_L("All the selected objects are on the locked plate,\nWe can not do auto-orient on these objects.")));
}
}
}
void OrientJob::prepare_selected() {
clear_input();
Model &model = m_plater->model();
std::vector<bool> obj_sel(model.objects.size(), false);
for (auto &s : m_plater->get_selection().get_content())
if (s.first < int(obj_sel.size()))
obj_sel[size_t(s.first)] = !s.second.empty();
//BBS: add only one plate mode
prepare_selection(obj_sel, false);
}
//BBS: prepare current part plate for orienting
void OrientJob::prepare_partplate() {
clear_input();
PartPlateList& plate_list = m_plater->get_partplate_list();
PartPlate* plate = plate_list.get_curr_plate();
assert(plate != nullptr);
if (plate->empty())
{
//no instances on this plate
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": no instances in current plate!");
return;
}
if (plate->is_locked()) {
m_plater->get_notification_manager()->push_notification(NotificationType::BBLPlateInfo,
NotificationManager::NotificationLevel::WarningNotificationLevel, into_u8(_L("This plate is locked,\nWe can not do auto-orient on this plate.")));
return;
}
Model& model = m_plater->model();
std::vector<bool> obj_sel(model.objects.size(), false);
// Go through the objects and check if inside the selection
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx)
{
ModelObject* mo = model.objects[oidx];
for (size_t inst_idx = 0; inst_idx < mo->instances.size(); ++inst_idx)
{
obj_sel[oidx] = plate->contain_instance(oidx, inst_idx);
}
}
prepare_selection(obj_sel, true);
}
//BBS: add partplate logic
void OrientJob::prepare()
{
int state = m_plater->get_prepare_state();
m_plater->get_notification_manager()->bbl_close_plateinfo_notification();
if (state == Job::JobPrepareState::PREPARE_STATE_DEFAULT) {
only_on_partplate = false;
prepare_selected();
}
else if (state == Job::JobPrepareState::PREPARE_STATE_MENU) {
only_on_partplate = true; // only arrange items on current plate
prepare_partplate();
}
}
void OrientJob::on_exception(const std::exception_ptr &eptr)
{
try {
if (eptr)
std::rethrow_exception(eptr);
} catch (std::exception &) {
PlaterJob::on_exception(eptr);
}
}
void OrientJob::process()
{
auto start = std::chrono::steady_clock::now();
static const auto arrangestr = _(L("Orienting..."));
const GLCanvas3D::OrientSettings& settings = m_plater->canvas3D()->get_orient_settings();
orientation::OrientParams params;
orientation::OrientParamsArea params_area;
if (settings.min_area) {
memcpy(&params, &params_area, sizeof(params));
params.min_volume = false;
}
else {
params.min_volume = true;
}
auto count = unsigned(m_selected.size() + m_unprintable.size());
params.stopcondition = [this]() { return was_canceled(); };
params.progressind = [this, count](unsigned st, std::string orientstr) {
st += m_unprintable.size();
if (st > 0) update_status(int(st / float(count) * 100), _L("Orienting") + " " + orientstr);
};
orientation::orient(m_selected, m_unselected, params);
auto time_elapsed = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - start);
std::stringstream ss;
if (!m_selected.empty())
ss << std::fixed << std::setprecision(3) << "Orient " << m_selected.back().name << " in " << time_elapsed.count() << " seconds. "
<< "Orientation: " << m_selected.back().orientation.transpose() << "; v,phi: " << m_selected.back().axis.transpose() << ", " << m_selected.back().angle << "; euler: " << m_selected.back().euler_angles.transpose();
// finalize just here.
//update_status(int(count),
// was_canceled() ? _(L("Orienting canceled."))
// : _(L(ss.str().c_str())));
wxGetApp().plater()->show_status_message(was_canceled() ? "Orienting canceled." : ss.str());
}
void OrientJob::finalize() {
// Ignore the arrange result if aborted.
if (was_canceled()) return;
for (OrientMesh& mesh : m_selected)
{
mesh.apply();
}
m_plater->update();
// BBS
//wxGetApp().obj_manipul()->set_dirty();
Job::finalize();
}
orientation::OrientMesh OrientJob::get_orient_mesh(ModelInstance* instance)
{
using OrientMesh = orientation::OrientMesh;
OrientMesh om;
auto obj = instance->get_object();
om.name = obj->name;
om.mesh = obj->mesh(); // don't know the difference to obj->raw_mesh(). Both seem OK
if (obj->config.has("support_threshold_angle"))
om.overhang_angle = obj->config.opt_int("support_threshold_angle");
else {
const Slic3r::DynamicPrintConfig& config = wxGetApp().preset_bundle->full_config();
om.overhang_angle = config.opt_int("support_threshold_angle");
}
om.setter = [instance](const OrientMesh& p) {
instance->rotate(p.rotation_matrix);
instance->get_object()->ensure_on_bed();
};
return om;
}
}} // namespace Slic3r::GUI

View file

@ -0,0 +1,66 @@
#ifndef ORIENTJOB_HPP
#define ORIENTJOB_HPP
#include "PlaterJob.hpp"
#include "libslic3r/Orient.hpp"
namespace Slic3r {
class ModelObject;
namespace GUI {
class OrientJob : public PlaterJob
{
using OrientMesh = orientation::OrientMesh;
using OrientMeshs = orientation::OrientMeshs;
OrientMeshs m_selected, m_unselected, m_unprintable;
// clear m_selected and m_unselected, reserve space for next usage
void clear_input();
//BBS: add only one plate mode
void prepare_selection(std::vector<bool> obj_sel, bool only_one_plate);
// Prepare the selected and unselected items separately. If nothing is
// selected, behaves as if everything would be selected.
void prepare_selected();
//BBS:prepare the items from current selected partplate
void prepare_partplate();
protected:
void prepare() override;
void on_exception(const std::exception_ptr &) override;
public:
OrientJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
: PlaterJob{std::move(pri), plater}
{}
void process() override;
void finalize() override;
#if 0
static
orientation::OrientMesh get_orient_mesh(ModelObject* obj, const Plater* plater)
{
using OrientMesh = orientation::OrientMesh;
OrientMesh om;
om.name = obj->name;
om.mesh = obj->mesh(); // don't know the difference to obj->raw_mesh(). Both seem OK
om.setter = [obj, plater](const OrientMesh& p) {
obj->rotate(p.angle, p.axis);
obj->ensure_on_bed();
};
return om;
}
#endif
static orientation::OrientMesh get_orient_mesh(ModelInstance* instance);
};
}} // namespace Slic3r::GUI
#endif // ORIENTJOB_HPP

View file

@ -0,0 +1,17 @@
#include "PlaterJob.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/Plater.hpp"
namespace Slic3r { namespace GUI {
void PlaterJob::on_exception(const std::exception_ptr &eptr)
{
try {
if (eptr)
std::rethrow_exception(eptr);
} catch (std::exception &e) {
show_error(m_plater, _(L("Exception")) + ": "+ e.what());
}
}
}} // namespace Slic3r::GUI

View file

@ -0,0 +1,26 @@
#ifndef PLATERJOB_HPP
#define PLATERJOB_HPP
#include "Job.hpp"
namespace Slic3r { namespace GUI {
class Plater;
class PlaterJob : public Job {
protected:
Plater *m_plater;
//BBS: add flag for whether on current part plate
bool only_on_partplate{false};
void on_exception(const std::exception_ptr &) override;
public:
PlaterJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater):
Job{std::move(pri)}, m_plater{plater} {}
};
}} // namespace Slic3r::GUI
#endif // PLATERJOB_HPP

View file

@ -0,0 +1,263 @@
#include "PrintJob.hpp"
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "bambu_networking.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
namespace Slic3r {
namespace GUI {
static wxString check_gcode_failed_str = _L("Abnormal print file data. Please slice again");
static wxString printjob_cancel_str = _L("Task canceled");
static wxString timeout_to_upload_str = _L("Upload task timed out. Please check the network problem and try again");
static wxString failed_in_cloud_service_str = _L("Cloud service connection failed. Please try again.");
static wxString file_is_not_exists_str = _L("Print file not found, please slice again");
static wxString file_over_size_str = _L("The print file exceeds the maximum allowable size (1GB). Please simplify the model and slice again");
static wxString print_canceled_str = _L("Task canceled");
static wxString upload_failed_str = _L("Failed uploading print file");
static wxString upload_login_failed_str = _L("Wrong Access code");
static wxString sending_over_lan_str = _L("Sending print job over LAN");
static wxString sending_over_cloud_str = _L("Sending print job through cloud service");
PrintJob::PrintJob(std::shared_ptr<ProgressIndicator> pri, Plater* plater, std::string dev_id)
: PlaterJob{ std::move(pri), plater },
m_dev_id(dev_id)
{
m_print_job_completed_id = plater->get_print_finished_event();
}
void PrintJob::prepare()
{
m_plater->get_print_job_data(&job_data);
}
void PrintJob::on_exception(const std::exception_ptr &eptr)
{
try {
if (eptr)
std::rethrow_exception(eptr);
} catch (std::exception &e) {
PlaterJob::on_exception(eptr);
}
}
void PrintJob::on_success(std::function<void()> success)
{
m_success_fun = success;
}
void PrintJob::process()
{
/* display info */
wxString msg;
int curr_percent = 10;
if (this->connection_type == "lan") {
msg = sending_over_lan_str;
}
else {
msg = sending_over_cloud_str;
}
int result = -1;
unsigned int http_code;
std::string http_body;
int total_plate_num = m_plater->get_partplate_list().get_plate_count();
PartPlate* plate = m_plater->get_partplate_list().get_plate(job_data.plate_idx);
if (plate == nullptr) {
plate = m_plater->get_partplate_list().get_curr_plate();
if (plate == nullptr)
return;
}
/* check gcode is valid */
if (!plate->is_valid_gcode_file()) {
update_status(curr_percent, check_gcode_failed_str);
return;
}
if (was_canceled()) {
update_status(curr_percent, printjob_cancel_str);
return;
}
std::string project_name = wxGetApp().plater()->get_project_name().ToUTF8().data();
int curr_plate_idx = 0;
if (job_data.plate_idx >= 0)
curr_plate_idx = job_data.plate_idx + 1;
else if (job_data.plate_idx == PLATE_CURRENT_IDX)
curr_plate_idx = m_plater->get_partplate_list().get_curr_plate_index() + 1;
BBL::PrintParams params;
params.dev_id = m_dev_id;
params.project_name = wxGetApp().plater()->get_project_name().ToUTF8().data();
params.preset_name = wxGetApp().preset_bundle->prints.get_selected_preset_name();
params.filename = job_data._3mf_path.string();
params.config_filename = job_data._3mf_config_path.string();
params.plate_index = curr_plate_idx;
params.task_bed_leveling = this->task_bed_leveling;
params.task_flow_cali = this->task_flow_cali;
params.task_vibration_cali = this->task_vibration_cali;
params.task_layer_inspect = this->task_layer_inspect;
params.task_record_timelapse= this->task_record_timelapse;
params.ams_mapping = this->task_ams_mapping;
params.connection_type = this->connection_type;
// local print access
params.dev_ip = m_dev_ip;
params.username = "bblp";
params.password = m_access_code;
auto update_fn = [this, &msg, &curr_percent](int stage, int code, std::string info) {
if (stage == BBL::SendingPrintJobStage::PrintingStageCreate) {
if (this->connection_type == "lan") {
msg = _L("Sending print job over LAN");
} else {
msg = _L("Sending print job through cloud service");
}
curr_percent = 25;
}
else if (stage == BBL::SendingPrintJobStage::PrintingStageUpload) {
curr_percent = 30;
if (code == 0 && !info.empty()) {
if (this->connection_type == "lan") {
msg = _L("Sending print job over LAN");
} else {
msg = _L("Sending print job through cloud service");
}
msg += wxString::Format("(%s)", info);
}
}
else if (stage == BBL::SendingPrintJobStage::PrintingStageWaiting) {
if (this->connection_type == "lan") {
msg = _L("Sending print job over LAN");
} else {
msg = _L("Sending print job through cloud service");
}
curr_percent = 50;
}
else if (stage == BBL::SendingPrintJobStage::PrintingStageRecord) {
curr_percent = 70;
msg = _L("Sending print configuration");
}
else if (stage == BBL::SendingPrintJobStage::PrintingStageSending) {
if (this->connection_type == "lan") {
msg = _L("Sending print job over LAN");
} else {
msg = _L("Sending print job through cloud service");
}
curr_percent = 90;
}
else if (stage == BBL::SendingPrintJobStage::PrintingStageFinished) {
curr_percent = 100;
msg = wxString::Format(_L("Successfully sent.Will automatically jump to the device page in %s s"), info);
} else {
if (this->connection_type == "lan") {
msg = _L("Sending print job over LAN");
} else {
msg = _L("Sending print job through cloud service");
}
}
this->update_status(curr_percent, msg);
};
auto cancel_fn = [this]() {
return was_canceled();
};
NetworkAgent* m_agent = wxGetApp().getAgent();
if (params.connection_type != "lan") {
if (!this->cloud_print_only
&& !params.password.empty()
&& !params.dev_ip.empty()
&& this->has_sdcard) {
// try to send local with record
BOOST_LOG_TRIVIAL(info) << "print_job: try to start local print with record";
this->update_status(curr_percent, _L("Sending print job over LAN"));
result = m_agent->start_local_print_with_record(params, update_fn, cancel_fn);
if (result < 0) {
// try to send with cloud
BOOST_LOG_TRIVIAL(warning) << "print_job: try to send with cloud";
this->update_status(curr_percent, _L("Sending print job through cloud service"));
result = m_agent->start_print(params, update_fn, cancel_fn);
}
} else {
BOOST_LOG_TRIVIAL(info) << "print_job: send with cloud";
this->update_status(curr_percent, _L("Sending print job through cloud service"));
result = m_agent->start_print(params, update_fn, cancel_fn);
if (result < 0) {
if (!params.password.empty() && !params.dev_ip.empty()) {
//try to send with local only
if (this->has_sdcard) {
this->update_status(curr_percent, _L("Sending print job over LAN"));
result = m_agent->start_local_print(params, update_fn, cancel_fn);
} else {
this->update_status(curr_percent, _L("Failed to connect to the cloud server connection. Please insert an SD card and resend the print job, which will transfer the print file via LAN. "));
BOOST_LOG_TRIVIAL(error) << "print_job: failed, need sdcard";
return;
}
}
}
}
} else {
if (this->has_sdcard) {
this->update_status(curr_percent, _L("Sending print job over LAN"));
result = m_agent->start_local_print(params, update_fn, cancel_fn);
} else {
this->update_status(curr_percent, _L("An SD card needs to be inserted before printing via LAN."));
return;
}
}
if (was_canceled()) {
update_status(curr_percent, printjob_cancel_str);
return;
}
if (result < 0) {
if (result == BAMBU_NETWORK_ERR_FTP_LOGIN_DENIED) {
update_status(curr_percent, upload_failed_str);
} if (result == BAMBU_NETWORK_ERR_FILE_NOT_EXIST) {
update_status(curr_percent, file_is_not_exists_str);
} else if (result == BAMBU_NETWORK_ERR_FILE_OVER_SIZE) {
update_status(curr_percent, file_over_size_str);
} else if (result == BAMBU_NETWORK_ERR_CHECK_MD5_FAILED) {
update_status(curr_percent, failed_in_cloud_service_str);
} else if (result == BAMBU_NETWORK_ERR_INVALID_PARAMS) {
update_status(curr_percent, upload_failed_str);
} else if (result == BAMBU_NETWORK_ERR_CANCELED) {
update_status(curr_percent, print_canceled_str);
} else if (result == BAMBU_NETWORK_ERR_TIMEOUT) {
update_status(curr_percent, timeout_to_upload_str);
} else if (result == BAMBU_NETWORK_ERR_INVALID_RESULT) {
update_status(curr_percent, upload_failed_str);
} else if (result == BAMBU_NETWORK_ERR_FTP_UPLOAD_FAILED) {
update_status(curr_percent, upload_failed_str);
} else {
update_status(curr_percent, failed_in_cloud_service_str);
}
BOOST_LOG_TRIVIAL(error) << "print_job: failed, result = " << result;
} else {
BOOST_LOG_TRIVIAL(error) << "print_job: send ok.";
wxCommandEvent* evt = new wxCommandEvent(m_print_job_completed_id);
evt->SetString(m_dev_id);
wxQueueEvent(m_plater, evt);
m_job_finished = true;
}
}
void PrintJob::finalize() {
if (was_canceled()) return;
Job::finalize();
}
}} // namespace Slic3r::GUI

View file

@ -0,0 +1,79 @@
#ifndef PrintJOB_HPP
#define PrintJOB_HPP
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include "PlaterJob.hpp"
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
class PrintPrepareData
{
public:
int plate_idx;
fs::path _3mf_path;
fs::path _3mf_config_path;
PrintPrepareData() {
plate_idx = 0;
}
};
class PrintJob : public PlaterJob
{
std::function<void()> m_success_fun{nullptr};
PrintPrepareData job_data;
std::string m_dev_id;
bool m_job_finished{ false };
int m_print_job_completed_id = 0;
protected:
void prepare() override;
void on_exception(const std::exception_ptr &) override;
public:
PrintJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater, std::string dev_id = "");
std::string m_dev_ip;
std::string m_access_code;
std::string task_bed_type;
bool task_bed_leveling;
bool task_flow_cali;
bool task_vibration_cali;
bool task_record_timelapse;
bool task_layer_inspect;
std::string task_ams_mapping;
std::string connection_type;
bool cloud_print_only { false };
bool has_sdcard { false };
void set_print_config(std::string bed_type, bool bed_leveling, bool flow_cali, bool vabration_cali, bool record_timelapse, bool layer_inspect)
{
task_bed_type = bed_type;
task_bed_leveling = bed_leveling;
task_flow_cali = flow_cali;
task_vibration_cali = vabration_cali;
task_record_timelapse = record_timelapse;
task_layer_inspect = layer_inspect;
}
int status_range() const override
{
return 100;
}
bool is_finished() { return m_job_finished; }
void set_print_job_finished_event(int event_id) { m_print_job_completed_id = event_id; }
void on_success(std::function<void()> success);
void process() override;
void finalize() override;
};
}} // namespace Slic3r::GUI
#endif

View file

@ -0,0 +1,29 @@
#ifndef IPROGRESSINDICATOR_HPP
#define IPROGRESSINDICATOR_HPP
#include <string>
#include <functional>
namespace Slic3r {
/**
* @brief Generic progress indication interface.
*/
class ProgressIndicator {
public:
/// Cancel callback function type
using CancelFn = std::function<void()>;
virtual ~ProgressIndicator() = default;
virtual void set_range(int range) = 0;
virtual void set_cancel_callback(CancelFn = CancelFn()) = 0;
virtual void set_progress(int pr) = 0;
virtual void set_status_text(const char *) = 0; // utf8 char array
virtual int get_range() const = 0;
};
}
#endif // IPROGRESSINDICATOR_HPP

View file

@ -0,0 +1,121 @@
#include "RotoptimizeJob.hpp"
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/SLA/Rotfinder.hpp"
#include "libslic3r/MinAreaBoundingBox.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "libslic3r/AppConfig.hpp"
namespace Slic3r { namespace GUI {
void RotoptimizeJob::prepare()
{
std::string accuracy_str =
wxGetApp().app_config->get("sla_auto_rotate", "accuracy");
std::string method_str =
wxGetApp().app_config->get("sla_auto_rotate", "method_id");
if (!accuracy_str.empty())
m_accuracy = std::stof(accuracy_str);
if (!method_str.empty())
m_method_id = std::stoi(method_str);
m_accuracy = std::max(0.f, std::min(m_accuracy, 1.f));
m_method_id = std::max(size_t(0), std::min(get_methods_count() - 1, m_method_id));
m_default_print_cfg = wxGetApp().preset_bundle->full_config();
const auto &sel = m_plater->get_selection().get_content();
m_selected_object_ids.clear();
m_selected_object_ids.reserve(sel.size());
for (const auto &s : sel) {
int obj_id;
std::tie(obj_id, std::ignore) = s;
m_selected_object_ids.emplace_back(obj_id);
}
}
void RotoptimizeJob::process()
{
int prev_status = 0;
auto params =
sla::RotOptimizeParams{}
.accuracy(m_accuracy)
.print_config(&m_default_print_cfg)
.statucb([this, &prev_status](int s)
{
if (s > 0 && s < 100)
;
// update_status(prev_status + s / m_selected_object_ids.size(),
// _(L("Searching for optimal orientation...")));
return !was_canceled();
});
for (ObjRot &objrot : m_selected_object_ids) {
ModelObject *o = m_plater->model().objects[size_t(objrot.idx)];
if (!o) continue;
if (Methods[m_method_id].findfn)
objrot.rot = Methods[m_method_id].findfn(*o, params);
prev_status += 100 / m_selected_object_ids.size();
if (was_canceled()) break;
}
// update_status(100, was_canceled() ? _(L("Orientation search canceled.")) :
// _(L("Orientation found.")));
}
void RotoptimizeJob::finalize()
{
if (was_canceled()) return;
for (const ObjRot &objrot : m_selected_object_ids) {
ModelObject *o = m_plater->model().objects[size_t(objrot.idx)];
if (!o) continue;
for(ModelInstance * oi : o->instances) {
if (objrot.rot)
oi->set_rotation({objrot.rot->x(), objrot.rot->y(), 0.});
auto trmatrix = oi->get_transformation().get_matrix();
Polygon trchull = o->convex_hull_2d(trmatrix);
MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
double phi = rotbb.angle_to_X();
// The box should be landscape
if(rotbb.width() < rotbb.height()) phi += PI / 2;
Vec3d rt = oi->get_rotation(); rt(Z) += phi;
oi->set_rotation(rt);
}
// 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 (!was_canceled())
m_plater->update();
Job::finalize();
}
}}

View file

@ -0,0 +1,76 @@
#ifndef ROTOPTIMIZEJOB_HPP
#define ROTOPTIMIZEJOB_HPP
#include "PlaterJob.hpp"
#include "libslic3r/SLA/Rotfinder.hpp"
#include "libslic3r/PrintConfig.hpp"
namespace Slic3r {
namespace GUI {
class RotoptimizeJob : public PlaterJob
{
using FindFn = std::function<Vec2d(const ModelObject & mo,
const sla::RotOptimizeParams &params)>;
struct FindMethod { std::string name; FindFn findfn; std::string descr; };
static inline const FindMethod Methods[]
= {{L("Best surface quality"),
sla::find_best_misalignment_rotation,
L("Optimize object rotation for best surface quality.")},
{L("Reduced overhang slopes"),
sla::find_least_supports_rotation,
L("Optimize object rotation to have minimum amount of overhangs needing support "
"structures.\nNote that this method will try to find the best surface of the object "
"for touching the print bed if no elevation is set.")},
// Just a min area bounding box that is done for all methods anyway.
{L("Lowest Z height"),
sla::find_min_z_height_rotation,
L("Rotate the model to have the lowest z height for faster print time.")}};
size_t m_method_id = 0;
float m_accuracy = 0.75;
DynamicPrintConfig m_default_print_cfg;
struct ObjRot
{
size_t idx;
std::optional<Vec2d> rot;
ObjRot(size_t id): idx{id}, rot{} {}
};
std::vector<ObjRot> m_selected_object_ids;
protected:
void prepare() override;
void process() override;
public:
RotoptimizeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
: PlaterJob{std::move(pri), plater}
{}
void finalize() override;
static constexpr size_t get_methods_count() { return std::size(Methods); }
static std::string get_method_name(size_t i)
{
return _utf8(Methods[i].name);
}
static std::string get_method_description(size_t i)
{
return _utf8(Methods[i].descr);
}
};
}} // namespace Slic3r::GUI
#endif // ROTOPTIMIZEJOB_HPP

View file

@ -0,0 +1,249 @@
#include "SLAImportJob.hpp"
#include "libslic3r/Format/SL1.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp"
#include <wx/dialog.h>
#include <wx/stattext.h>
#include <wx/combobox.h>
#include <wx/filename.h>
#include <wx/filepicker.h>
namespace Slic3r { namespace GUI {
enum class Sel { modelAndProfile, profileOnly, modelOnly};
class ImportDlg: public wxDialog {
wxFilePickerCtrl *m_filepicker;
wxComboBox *m_import_dropdown, *m_quality_dropdown;
public:
ImportDlg(Plater *plater)
: wxDialog{plater, wxID_ANY, "Import SLA archive"}
{
auto szvert = new wxBoxSizer{wxVERTICAL};
auto szfilepck = new wxBoxSizer{wxHORIZONTAL};
m_filepicker = new wxFilePickerCtrl(this, wxID_ANY,
from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")),
"SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP",
wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST);
szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER);
szfilepck->Add(m_filepicker, 1);
szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5);
auto szchoices = new wxBoxSizer{wxHORIZONTAL};
static const std::vector<wxString> inp_choices = {
_(L("Import model and profile")),
_(L("Import profile only")),
_(L("Import model only"))
};
m_import_dropdown = new wxComboBox(
this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize,
inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
szchoices->Add(m_import_dropdown);
szchoices->Add(new wxStaticText(this, wxID_ANY, _L("Quality") + ": "), 0, wxALIGN_CENTER | wxALL, 5);
static const std::vector<wxString> qual_choices = {
_(L("Accurate")),
_(L("Balanced")),
_(L("Quick"))
};
m_quality_dropdown = new wxComboBox(
this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize,
qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
szchoices->Add(m_quality_dropdown);
m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
if (get_selection() == Sel::profileOnly)
m_quality_dropdown->Disable();
else m_quality_dropdown->Enable();
});
szvert->Add(szchoices, 0, wxALL, 5);
szvert->AddStretchSpacer(1);
auto szbtn = new wxBoxSizer(wxHORIZONTAL);
szbtn->Add(new wxButton{this, wxID_CANCEL});
szbtn->Add(new wxButton{this, wxID_OK});
szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5);
SetSizerAndFit(szvert);
}
Sel get_selection() const
{
int sel = m_import_dropdown->GetSelection();
return Sel(std::min(int(Sel::modelOnly), std::max(0, sel)));
}
Vec2i get_marchsq_windowsize() const
{
enum { Accurate, Balanced, Fast};
switch(m_quality_dropdown->GetSelection())
{
case Fast: return {8, 8};
case Balanced: return {4, 4};
default:
case Accurate:
return {2, 2};
}
}
wxString get_path() const
{
return m_filepicker->GetPath();
}
};
class SLAImportJob::priv {
public:
Plater *plater;
Sel sel = Sel::modelAndProfile;
indexed_triangle_set mesh;
DynamicPrintConfig profile;
wxString path;
Vec2i win = {2, 2};
std::string err;
ConfigSubstitutions config_substitutions;
ImportDlg import_dlg;
priv(Plater *plt) : plater{plt}, import_dlg{plt} {}
};
SLAImportJob::SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
: PlaterJob{std::move(pri), plater}, p{std::make_unique<priv>(plater)}
{}
SLAImportJob::~SLAImportJob() = default;
void SLAImportJob::process()
{
auto progr = [this](int s) {
if (s < 100)
update_status(int(s), _(L("Importing SLA archive")));
return !was_canceled();
};
if (p->path.empty()) return;
std::string path = p->path.ToUTF8().data();
try {
switch (p->sel) {
case Sel::modelAndProfile:
case Sel::modelOnly:
p->config_substitutions = import_sla_archive(path, p->win, p->mesh, p->profile, progr);
break;
case Sel::profileOnly:
p->config_substitutions = import_sla_archive(path, p->profile);
break;
}
} catch (MissingProfileError &) {
p->err = _L("The SLA archive doesn't contain any presets. "
"Please activate some SLA printer preset first before importing that SLA archive.").ToStdString();
} catch (std::exception &ex) {
p->err = ex.what();
}
update_status(100, was_canceled() ? _(L("Importing canceled.")) :
_(L("Importing done.")));
}
void SLAImportJob::reset()
{
p->sel = Sel::modelAndProfile;
p->mesh = {};
p->profile = m_plater->sla_print().full_print_config();
p->win = {2, 2};
p->path.Clear();
}
void SLAImportJob::prepare()
{
reset();
if (p->import_dlg.ShowModal() == wxID_OK) {
auto path = p->import_dlg.get_path();
auto nm = wxFileName(path);
p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath();
p->sel = p->import_dlg.get_selection();
p->win = p->import_dlg.get_marchsq_windowsize();
p->config_substitutions.clear();
} else {
p->path = "";
}
}
void SLAImportJob::finalize()
{
// Ignore the arrange result if aborted.
if (was_canceled()) return;
if (!p->err.empty()) {
show_error(p->plater, p->err);
p->err = "";
return;
}
std::string name = wxFileName(p->path).GetName().ToUTF8().data();
if (p->profile.empty()) {
m_plater->get_notification_manager()->push_notification(
NotificationType::CustomNotification,
NotificationManager::NotificationLevel::WarningNotificationLevel,
_L("The imported SLA archive did not contain any presets. "
"The current SLA presets were used as fallback.").ToStdString());
}
if (p->sel != Sel::modelOnly) {
if (p->profile.empty())
p->profile = m_plater->sla_print().full_print_config();
const ModelObjectPtrs& objects = p->plater->model().objects;
for (auto object : objects)
if (object->volumes.size() > 1)
{
Slic3r::GUI::show_info(nullptr,
_(L("You cannot load SLA project with a multi-part object on the bed")) + "\n\n" +
_(L("Please check your object list before preset changing.")),
_(L("Attention!")) );
return;
}
DynamicPrintConfig config = {};
config.apply(SLAFullPrintConfig::defaults());
config += std::move(p->profile);
wxGetApp().preset_bundle->load_config_model(name, std::move(config));
wxGetApp().load_current_presets();
}
if (!p->mesh.empty()) {
bool is_centered = false;
p->plater->sidebar().obj_list()->load_mesh_object(TriangleMesh{std::move(p->mesh)},
name, is_centered);
}
if (! p->config_substitutions.empty())
show_substitutions_info(p->config_substitutions, p->path.ToUTF8().data());
reset();
}
}} // namespace Slic3r::GUI

View file

@ -0,0 +1,27 @@
#ifndef SLAIMPORTJOB_HPP
#define SLAIMPORTJOB_HPP
#include "PlaterJob.hpp"
namespace Slic3r { namespace GUI {
class SLAImportJob : public PlaterJob {
class priv;
std::unique_ptr<priv> p;
protected:
void prepare() override;
void process() override;
void finalize() override;
public:
SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater);
~SLAImportJob();
void reset();
};
}} // namespace Slic3r::GUI
#endif // SLAIMPORTJOB_HPP