This commit is contained in:
QIDI TECH
2024-09-03 09:34:33 +08:00
parent 27f34aa3e8
commit 585146181b
5147 changed files with 1734881 additions and 0 deletions

View File

@@ -0,0 +1,798 @@
#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;
//QDS
ret.name = "WipeTower";
ret.is_virt_object = true;
ret.is_wipe_tower = true;
++ret.priority;
BOOST_LOG_TRIVIAL(debug) << " arrange: wipe tower info:" << m_bb << ", m_pos: " << m_pos.transpose();
return ret;
}
};
// QDS: 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_uncompatible_plates.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;
//QDS: 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]);
//QDS: 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%, name %3%") % oidx % i % mo->name;
}
}
}
// 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::QDTPlateInfo,
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.
//QDS: remove logic for unselected object
//for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride;
}
void ArrangeJob::prepare_all() {
clear_input();
PartPlateList& plate_list = m_plater->get_partplate_list();
for (size_t i = 0; i < plate_list.get_plate_count(); i++) {
PartPlate* plate = plate_list.get_plate(i);
bool same_as_global_print_seq = true;
plate->get_real_print_seq(&same_as_global_print_seq);
if (plate->is_locked() == false && !same_as_global_print_seq) {
plate->lock(true);
m_uncompatible_plates.push_back(i);
}
}
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]);
//QDS: 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::QDTPlateInfo,
NotificationManager::NotificationLevel::WarningNotificationLevel, into_u8(_L("No arrangeable objects are selected.")));
}
else {
m_plater->get_notification_manager()->push_notification(NotificationType::QDTPlateInfo,
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();
// add the virtual object into unselect list if has
plate_list.preprocess_exclude_areas(m_unselected, MAX_NUM_PLATES);
}
arrangement::ArrangePolygon estimate_wipe_tower_info(int plate_index, std::set<int>& extruder_ids)
{
PartPlateList& ppl = wxGetApp().plater()->get_partplate_list();
const auto& full_config = wxGetApp().preset_bundle->full_config();
int plate_count = ppl.get_plate_count();
int plate_index_valid = std::min(plate_index, plate_count - 1);
// we have to estimate the depth using the extruder number of all plates
int extruder_size = extruder_ids.size();
auto arrange_poly = ppl.get_plate(plate_index_valid)->estimate_wipe_tower_polygon(full_config, plate_index, extruder_size);
arrange_poly.bed_idx = plate_index;
return arrange_poly;
}
// 准备料塔。逻辑如下:
// 1. 以下几种情况不需要料塔:
// 1料塔被禁用
// 2逐件打印
// 3不允许不同材料落在相同盘且没有多色对象
// 2. 以下情况需要料塔:
// 1某对象是多色对象
// 2打开了支撑且支撑体与接触面使用的是不同材料
// 3允许不同材料落在相同盘且所有选定对象中使用了多种热床温度相同的材料
// 所有对象都是单色的但不同对象的材料不同例如对象A使用红色PLA对象B使用白色PLA
void ArrangeJob::prepare_wipe_tower()
{
bool need_wipe_tower = false;
// if wipe tower is explicitly disabled, no need to estimate
DynamicPrintConfig& current_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
auto op = current_config.option("enable_prime_tower");
bool enable_prime_tower = op && op->getBool();
if (!enable_prime_tower || params.is_seq_print) return;
bool smooth_timelapse = false;
auto sop = current_config.option("timelapse_type");
if (sop) { smooth_timelapse = sop->getInt() == TimelapseType::tlSmooth; }
if (smooth_timelapse) { need_wipe_tower = true; }
// estimate if we need wipe tower for all plates:
// need wipe tower if some object has multiple extruders (has paint-on colors or support material)
for (const auto& item : m_selected) {
std::set<int> obj_extruders;
obj_extruders.insert(item.extrude_ids.begin(), item.extrude_ids.end());
if (obj_extruders.size() > 1) {
need_wipe_tower = true;
BOOST_LOG_TRIVIAL(info) << "arrange: need wipe tower because object " << item.name << " has multiple extruders (has paint-on colors)";
break;
}
}
// if multile extruders have same bed temp, we need wipe tower
// 允许不同材料落在相同盘,且所有选定对象中使用了多种热床温度相同的材料
if (params.allow_multi_materials_on_same_plate) {
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;
BOOST_LOG_TRIVIAL(info) << "arrange: need wipe tower because allow_multi_materials_on_same_plate=true and we have multiple extruders of same type";
break;
}
}
}
BOOST_LOG_TRIVIAL(info) << "arrange: need_wipe_tower=" << need_wipe_tower;
ArrangePolygon wipe_tower_ap;
wipe_tower_ap.name = "WipeTower";
wipe_tower_ap.is_virt_object = true;
wipe_tower_ap.is_wipe_tower = true;
const GLCanvas3D* canvas3D = static_cast<const GLCanvas3D*>(m_plater->canvas3D());
std::set<int> extruder_ids;
PartPlateList& ppl = wxGetApp().plater()->get_partplate_list();
int plate_count = ppl.get_plate_count();
if (!only_on_partplate) {
extruder_ids = ppl.get_extruders(true);
}
int bedid_unlocked = 0;
for (int bedid = 0; bedid < MAX_NUM_PLATES; bedid++) {
int plate_index_valid = std::min(bedid, plate_count - 1);
PartPlate* pl = ppl.get_plate(plate_index_valid);
if(bedid<plate_count && pl->is_locked())
continue;
if (auto wti = get_wipe_tower(*m_plater, bedid)) {
// wipe tower is already there
wipe_tower_ap = get_wipetower_arrange_poly(&wti);
wipe_tower_ap.bed_idx = bedid_unlocked;
m_unselected.emplace_back(wipe_tower_ap);
}
else if (need_wipe_tower) {
if (only_on_partplate) {
auto plate_extruders = pl->get_extruders(true);
extruder_ids.clear();
extruder_ids.insert(plate_extruders.begin(), plate_extruders.end());
}
wipe_tower_ap = estimate_wipe_tower_info(bedid, extruder_ids);
wipe_tower_ap.bed_idx = bedid_unlocked;
m_unselected.emplace_back(wipe_tower_ap);
}
bedid_unlocked++;
}
}
//QDS: 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::QDTPlateInfo,
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) || plate->intersect_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;
bool locked = plate_list.preprocess_arrange_polygon_other_locked(oidx, inst_idx, ap, in_plate);
if (!locked)
{
ap.itemid = cont.size();
cont.emplace_back(std::move(ap));
}
else
{
//skip this object due to be not in current plate, treated as locked
ap.itemid = m_locked.size();
m_locked.emplace_back(std::move(ap));
//BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, name %2%") % oidx % mo->name;
}
}
}
// QDS
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));
}
// add the virtual object into unselect list if has
plate_list.preprocess_exclude_areas(m_unselected, current_plate_index + 1);
}
//QDS: 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()->qdt_close_plateinfo_notification();
params = init_arrange_params(m_plater);
//QDS update extruder params and speed table before arranging
const Slic3r::DynamicPrintConfig& config = wxGetApp().preset_bundle->full_config();
auto& print = wxGetApp().plater()->get_partplate_list().get_current_fff_print();
auto print_config = print.config();
int numExtruders = wxGetApp().preset_bundle->filament_presets.size();
Model::setExtruderParams(config, numExtruders);
Model::setPrintSpeedTable(config, print_config);
int state = m_plater->get_prepare_state();
if (state == Job::JobPrepareState::PREPARE_STATE_DEFAULT) {
only_on_partplate = false;
prepare_all();
}
else if (state == Job::JobPrepareState::PREPARE_STATE_MENU) {
only_on_partplate = true; // only arrange items on current plate
prepare_partplate();
}
#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(get_extents(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 || it->height>params.printable_height)
{
#if SAVE_ARRANGE_POLY
SVG svg(data_dir() + "/SVG/arrange_unprintable_"+it->name+".svg", get_extents(it->poly));
if (svg.is_opened())
svg.draw_outline(it->poly);
#endif
if (it->poly.area() < 0.001) {
auto msg = (boost::format(_u8L("Object %1% has zero size and can't be arranged.")) % it->name).str();
m_plater->get_notification_manager()->push_notification(NotificationType::QDTPlateInfo,
NotificationManager::NotificationLevel::WarningNotificationLevel, msg);
}
m_unprintable.push_back(*it);
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()
{
auto & partplate_list = m_plater->get_partplate_list();
const Slic3r::DynamicPrintConfig& global_config = wxGetApp().preset_bundle->full_config();
if (params.avoid_extrusion_cali_region && global_config.opt_bool("scan_first_layer"))
partplate_list.preprocess_nonprefered_areas(m_unselected, MAX_NUM_PLATES);
update_arrange_params(params, m_plater->config(), m_selected);
update_selected_items_inflation(m_selected, m_plater->config(), params);
update_unselected_items_inflation(m_unselected, m_plater->config(), params);
update_selected_items_axis_align(m_selected, m_plater->config(), params);
Points bedpts = get_shrink_bedpts(m_plater->config(),params);
partplate_list.preprocess_exclude_areas(params.excluded_regions, 1, scale_(1));
BOOST_LOG_TRIVIAL(debug) << "arrange bedpts:" << bedpts[0].transpose() << ", " << bedpts[1].transpose() << ", " << bedpts[2].transpose() << ", " << bedpts[3].transpose();
params.stopcondition = [this]() { return was_canceled(); };
params.progressind = [this](unsigned num_finished, std::string str = "") {
update_status(num_finished, _L("Arranging") + " "+ wxString::FromUTF8(str));
};
{
BOOST_LOG_TRIVIAL(warning)<< "Arrange full params: "<< params.to_json();
BOOST_LOG_TRIVIAL(info) << boost::format("arrange: items selected before arranging: %1%") % m_selected.size();
for (auto selected : m_selected) {
BOOST_LOG_TRIVIAL(debug) << selected.name << ", extruder: " << selected.extrude_ids.back() << ", bed: " << selected.bed_idx << ", filemant_type:" << selected.filament_temp_type
<< ", trans: " << selected.translation.transpose();
}
BOOST_LOG_TRIVIAL(debug) << "arrange: items unselected before arrange: " << m_unselected.size();
for (auto item : m_unselected)
BOOST_LOG_TRIVIAL(debug) << item.name << ", bed: " << item.bed_idx << ", trans: " << item.translation.transpose()
<<", bbox:"<<get_extents(item.poly).min.transpose()<<","<<get_extents(item.poly).max.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(info) << boost::format("arrange: items selected after arranging: %1%") % m_selected.size();
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
<< ", trans: " << unscale<double>(selected.translation(X)) << ","<< unscale<double>(selected.translation(Y));
BOOST_LOG_TRIVIAL(debug) << "arrange: items unselected after arrange: "<< m_unselected.size();
for (auto item : m_unselected)
BOOST_LOG_TRIVIAL(debug) << item.name << ", bed: " << item.bed_idx << ", trans: " << item.translation.transpose();
}
// 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) {
//QDS: already processed in m_selected
//m_unprintable.push_back(std::move(item));
we_have_unpackable_items = true;
}
}
// finalize just here.
update_status(status_range(),
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()) {
// Unprintable items go to the last virtual bed
int beds = 0;
//QDS: partplate
PartPlateList& plate_list = m_plater->get_partplate_list();
//clear all the relations before apply the arrangement results
if (only_on_partplate) {
plate_list.clear(false, false, true, current_plate_index);
}
else
plate_list.clear(false, false, true, -1);
//QDS: 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
//QDS: 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;
}
//QDS: adjust the bed_index, create new plates, get the max bed_index
for (ArrangePolygon& ap : m_unselected) {
if (ap.is_virt_object)
continue;
//QDS: 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) {
//QDS: 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;
//QDS: 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();
// QDS
//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")));
}
// unlock the plates we just locked
for (int i : m_uncompatible_plates) {
PartPlate* plate = plate_list.get_plate(i);
if (plate) plate->lock(false);
}
//QDS: reload all objects due to arrange
if (only_on_partplate) {
plate_list.rebuild_plates_after_arrangement(!only_on_partplate, true, current_plate_index);
}
else {
plate_list.rebuild_plates_after_arrangement(!only_on_partplate, true);
}
// QDS: update slice context and gcode result.
m_plater->update_slicing_context_to_current_partplate();
wxGetApp().obj_list()->reload_all_plates();
m_plater->update();
m_plater->get_notification_manager()->push_notification(NotificationType::ArrangeOngoing,
NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("Arranging done."));
}
else {
m_plater->get_notification_manager()->push_notification(NotificationType::ArrangeOngoing,
NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("Arranging canceled."));
}
Job::finalize();
m_plater->m_arrange_running.store(false);
}
std::optional<arrangement::ArrangePolygon>
get_wipe_tower_arrangepoly(const Plater &plater)
{
int id = plater.canvas3D()->fff_print()->get_plate_index();
if (auto wti = get_wipe_tower(plater, id))
return get_wipetower_arrange_poly(&wti);
return {};
}
//QDS: add sudoku-style stride
double bed_stride_x(const Plater* plater) {
double bedwidth = plater->build_volume().bounding_box().size().x();
return (1. + LOGICAL_BED_GAP) * bedwidth;
}
double bed_stride_y(const Plater* plater) {
double beddepth = plater->build_volume().bounding_box().size().y();
return (1. + LOGICAL_BED_GAP) * beddepth;
}
// call before get selected and unselected
arrangement::ArrangeParams init_arrange_params(Plater *p)
{
arrangement::ArrangeParams params;
GLCanvas3D::ArrangeSettings &settings = p->canvas3D()->get_arrange_settings();
auto &print = wxGetApp().plater()->get_partplate_list().get_current_fff_print();
const PrintConfig &print_config = print.config();
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_max_radius.value;
params.printable_height = print_config.printable_height.value;
params.nozzle_height = print.config().nozzle_height.value;
params.align_center = print_config.best_object_pos.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);
params.align_to_y_axis = settings.align_to_y_axis;
int state = p->get_prepare_state();
if (state == Job::JobPrepareState::PREPARE_STATE_MENU) {
PartPlateList &plate_list = p->get_partplate_list();
PartPlate * plate = plate_list.get_curr_plate();
bool plate_same_as_global = true;
params.is_seq_print = plate->get_real_print_seq(&plate_same_as_global) == PrintSequence::ByObject;
// if plate's print sequence is not the same as global, the settings.distance is no longer valid, we set it to auto
if (!plate_same_as_global)
params.min_obj_distance = 0;
}
if (params.is_seq_print) {
params.bed_shrink_x = BED_SHRINK_SEQ_PRINT;
params.bed_shrink_y = BED_SHRINK_SEQ_PRINT;
}
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;
//QDS: 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
std::vector<int> m_uncompatible_plates; // plate indices with different printing sequence than global
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();
//QDS:prepare the items from current selected partplate
void prepare_partplate();
void prepare_wipe_tower();
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
{
// ensure finalize() is called after all operations in process() is finished.
return int(m_selected.size() + m_unprintable.size() + 1);
}
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.;
//QDS: 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 init_arrange_params(Plater *p);
}} // namespace Slic3r::GUI
#endif // ARRANGEJOB_HPP

View File

@@ -0,0 +1,165 @@
#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");
BindJob::BindJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater, std::string dev_id, std::string dev_ip, std::string sec_link,
std::string ssdp_version)
: PlaterJob{std::move(pri), plater},
m_dev_id(dev_id),
m_dev_ip(dev_ip),
m_sec_link(sec_link),
m_ssdp_version(ssdp_version)
{
;
}
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);
wxCommandEvent event(EVT_BIND_UPDATE_MESSAGE);
event.SetString(msg);
event.SetEventObject(m_event_handle);
wxPostEvent(m_event_handle, event);
}
void BindJob::process()
{
int result_code = 0;
std::string result_info;
/* 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);
m_agent->track_update_property("ssdp_version", m_ssdp_version, "string");
int result = m_agent->bind(m_dev_ip, m_dev_id, m_sec_link, timezone, m_improved,
[this, &curr_percent, &msg, &result_code, &result_info](int stage, int code, std::string info) {
result_code = code;
result_info = info;
if (stage == QDT::BindJobStage::LoginStageConnect) {
curr_percent = 15;
msg = _L("Logging in");
} else if (stage == QDT::BindJobStage::LoginStageLogin) {
curr_percent = 30;
msg = _L("Logging in");
} else if (stage == QDT::BindJobStage::LoginStageWaitForLogin) {
curr_percent = 45;
msg = _L("Logging in");
} else if (stage == QDT::BindJobStage::LoginStageGetIdentify) {
curr_percent = 60;
msg = _L("Logging in");
} else if (stage == QDT::BindJobStage::LoginStageWaitAuth) {
curr_percent = 80;
msg = _L("Logging in");
} else if (stage == QDT::BindJobStage::LoginStageFinished) {
curr_percent = 100;
msg = _L("Logging in");
} else {
msg = _L("Logging in");
}
if (code != 0) {
msg = _L("Login failed");
if (code == QIDI_NETWORK_ERR_TIMEOUT) {
msg += _L("Please check the printer network connection.");
}
}
update_status(curr_percent, msg);
}
);
if (result < 0) {
BOOST_LOG_TRIVIAL(trace) << "login: result = " << result;
if (result_code == QIDI_NETWORK_ERR_BIND_ECODE_LOGIN_REPORT_FAILED || result_code == QIDI_NETWORK_ERR_BIND_GET_PRINTER_TICKET_TIMEOUT) {
int error_code;
try
{
error_code = stoi(result_info);
result_info = wxGetApp().get_hms_query()->query_print_error_msg(error_code).ToStdString();
}
catch (...) {
;
}
}
post_fail_event(result_code, result_info);
return;
}
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) {
BOOST_LOG_TRIVIAL(trace) << "login: dev is null";
post_fail_event(result_code, result_info);
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(int code, std::string info)
{
wxCommandEvent event(EVT_BIND_MACHINE_FAIL);
event.SetInt(code);
event.SetString(info);
event.SetEventObject(m_event_handle);
wxPostEvent(m_event_handle, event);
}
}} // namespace Slic3r::GUI

View File

@@ -0,0 +1,52 @@
#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;
std::string m_sec_link;
std::string m_ssdp_version;
bool m_job_finished{ false };
int m_print_job_completed_id = 0;
bool m_improved{false};
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,
std::string sec_link, std::string ssdp_version);
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(int code, std::string info);
void set_improved(bool improved){m_improved = improved;};
};
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,324 @@
#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 "libnest2d/common.hpp"
#include <numeric>
namespace Slic3r {
namespace GUI {
//QDS: 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();
params = init_arrange_params(m_plater);
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;
const Slic3r::DynamicPrintConfig& global_config = wxGetApp().preset_bundle->full_config();
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_instance_arrange_poly(mo->instances[inst_idx], global_config);
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
double scaled_exclusion_gap = scale_(1);
plate_list.preprocess_exclude_areas(params.excluded_regions, 1, scaled_exclusion_gap);
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.);
auto polys = offset_ex(m_selected.front().poly, params.min_obj_distance / 2);
ExPolygon poly = polys.empty() ? m_selected.front().poly : polys.front();
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) {
//QDS: 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_instance_arrange_poly(mi, global_config);
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];
ModelObject* newObj = m_plater->model().add_object(*mo);
newObj->name = mo->name +" "+ std::to_string(p.itemid);
for (ModelInstance *newInst : newObj->instances) { newInst->apply_arrange_result(p.translation.cast<double>(), p.rotation); }
//m_plater->sidebar().obj_list()->paste_objects_into_list({m_plater->model().objects.size()-1});
};
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.
//QDS: 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;
update_arrange_params(params, m_plater->config(), m_selected);
m_bedpts = get_shrink_bedpts(m_plater->config(), params);
auto &partplate_list = m_plater->get_partplate_list();
auto &print = wxGetApp().plater()->get_partplate_list().get_current_fff_print();
const Slic3r::DynamicPrintConfig& global_config = wxGetApp().preset_bundle->full_config();
if (params.avoid_extrusion_cali_region && global_config.opt_bool("scan_first_layer"))
partplate_list.preprocess_nonprefered_areas(m_unselected, MAX_NUM_PLATES);
update_selected_items_inflation(m_selected, m_plater->config(), params);
update_unselected_items_inflation(m_unselected, m_plater->config(), params);
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(st, _L("Filling") + " " + wxString::FromUTF8(str));
};
params.on_packed = [&do_stop] (const ArrangePolygon &ap) {
do_stop = ap.bed_idx > 0 && ap.priority == 0;
};
// final align用的是凸包在有fixed item的情况下可能找到的参考点位置是错的这里就不做了。见STUDIO-3265
params.do_final_align = false;
if (m_selected.size() > 100){
// too many items, just find grid empty cells to put them
Vec2f step = unscaled<float>(get_extents(m_selected.front().poly).size()) + Vec2f(m_selected.front().brim_width, m_selected.front().brim_width);
std::vector<Vec2f> empty_cells = Plater::get_empty_cells(step);
size_t n=std::min(m_selected.size(), empty_cells.size());
for (size_t i = 0; i < n; i++) {
m_selected[i].translation = scaled<coord_t>(empty_cells[i]);
m_selected[i].bed_idx= 0;
}
for (size_t i = n; i < m_selected.size(); i++) {
m_selected[i].bed_idx = -1;
}
}
else
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;
//QDS: 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);
});
int oldSize = m_plater->model().objects.size();
if (added_cnt > 0) {
//QDS: adjust the selected instances
for (ArrangePolygon& ap : m_selected) {
if (ap.bed_idx != 0) {
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":skipped: bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale<double>(ap.translation(X)) % unscale<double>(ap.translation(Y));
/*if (ap.itemid == -1)*/
continue;
ap.bed_idx = plate_list.get_plate_count();
}
else
ap.bed_idx = cur_plate;
if (m_selected.size() <= 100) {
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));
}
int newSize = m_plater->model().objects.size();
auto obj_list = m_plater->sidebar().obj_list();
for (size_t i = oldSize; i < newSize; i++) {
obj_list->add_object_to_list(i, true, true, false);
obj_list->update_printable_state(i, 0);
}
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": paste_objects_into_list";
/*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();
//BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": model_object->ensure_on_bed()";
m_plater->update();
}
Job::finalize();
}
}} // namespace Slic3r::GUI

View File

@@ -0,0 +1,48 @@
#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;
//QDS: add partplate related logic
ArrangePolygons m_locked;;
Points m_bedpts;
arrangement::ArrangeParams params;
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

167
src/slic3r/GUI/Jobs/Job.cpp Normal file
View File

@@ -0,0 +1,167 @@
#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);
}
void GUI::Job::update_percent_finish()
{
m_progress->clear_percent();
}
void GUI::Job::show_error_info(wxString msg, int code, wxString description, wxString extra)
{
m_progress->show_error_info(msg, code, description, extra);
}
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();
});
}
}

132
src/slic3r/GUI/Jobs/Job.hpp Normal file
View File

@@ -0,0 +1,132 @@
#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 = "");
void update_percent_finish();
void show_error_info(wxString msg, int code, wxString description, wxString extra);
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,43 @@
#include "NotificationProgressIndicator.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
namespace Slic3r { namespace GUI {
NotificationProgressIndicator::NotificationProgressIndicator(NotificationManager *nm): m_nm{nm} {}
void NotificationProgressIndicator::clear_percent()
{
}
void NotificationProgressIndicator::show_error_info(wxString msg, int code, wxString description, wxString extra)
{
}
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,28 @@
#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 clear_percent() override;
void show_error_info(wxString msg, int code, wxString description, wxString extra) override;
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,238 @@
#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);
}
//QDS: 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::QDTPlateInfo,
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();
//QDS: add only one plate mode
prepare_selection(obj_sel, false);
}
//QDS: 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::QDTPlateInfo,
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);
}
//QDS: add partplate logic
void OrientJob::prepare()
{
int state = m_plater->get_prepare_state();
m_plater->get_notification_manager()->qdt_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()) {
for (OrientMesh& mesh : m_selected) {
mesh.apply();
}
m_plater->update();
// QDS
//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();
//QDS: 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();
//QDS: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;
//QDS: 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,678 @@
#include "PrintJob.hpp"
#include <regex>
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "qidi_networking.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 status 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 send_print_failed_str = _L("Failed to send the print job. Please try again.");
static wxString upload_ftp_failed_str = _L("Failed to upload file to ftp. Please try again.");
static wxString desc_network_error = _L("Check the current status of the qidi server by clicking on the link above.");
static wxString desc_file_too_large = _L("The size of the print file is too large. Please adjust the file size and try again.");
static wxString desc_fail_not_exist = _L("Print file not found, Please slice it again and send it for printing.");
static wxString desc_upload_ftp_failed = _L("Failed to upload print file to FTP. Please check the network status and try again.");
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");
static wxString wait_sending_finish = _L("Print task sending times out.");
//static wxString desc_wait_sending_finish = _L("The printer timed out while receiving a print job. Please check if the network is functioning properly and send the print again.");
//static wxString desc_wait_sending_finish = _L("The printer timed out while receiving a print job. Please check if the network is functioning properly.");
PrintJob::PrintJob(std::shared_ptr<ProgressIndicator> pri, Plater* plater, std::string dev_id)
: PlaterJob{ std::move(pri), plater },
m_dev_id(dev_id),
m_is_calibration_task(false)
{
m_print_job_completed_id = plater->get_print_finished_event();
}
void PrintJob::prepare()
{
if (job_data.is_from_plater)
m_plater->get_print_job_data(&job_data);
if (&job_data) {
std::string temp_file = Slic3r::resources_dir() + "/check_access_code.txt";
auto check_access_code_path = temp_file.c_str();
BOOST_LOG_TRIVIAL(trace) << "sned_job: check_access_code_path = " << check_access_code_path;
job_data._temp_path = fs::path(check_access_code_path);
}
}
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;
}
std::string PrintJob::truncate_string(const std::string& str, size_t maxLength)
{
if (str.length() <= maxLength)
{
return str;
}
wxString local_str = wxString::FromUTF8(str);
wxString truncatedStr;
for (auto i = 1; i < local_str.Length(); i++) {
wxString tagStr = local_str.Mid(0, i);
if (tagStr.ToUTF8().length() >= maxLength) {
truncatedStr = local_str.Mid(0, i - 1);
break;
}
}
return truncatedStr.utf8_string();
}
wxString PrintJob::get_http_error_msg(unsigned int status, std::string body)
{
try {
int code = 0;
std::string error;
std::string message;
wxString result;
if (status >= 400 && status < 500)
try {
json j = json::parse(body);
if (j.contains("code")) {
if (!j["code"].is_null())
code = j["code"].get<int>();
}
if (j.contains("error")) {
if (!j["error"].is_null())
error = j["error"].get<std::string>();
}
if (j.contains("message")) {
if (!j["message"].is_null())
message = j["message"].get<std::string>();
}
switch (status) {
;
}
}
catch (...) {
;
}
else if (status == 503) {
return _L("Service Unavailable");
}
else {
wxString unkown_text = _L("Unknown Error.");
unkown_text += wxString::Format("status=%u, body=%s", status, body);
BOOST_LOG_TRIVIAL(error) << "http_error: status=" << status << ", code=" << code << ", error=" << error;
return unkown_text;
}
BOOST_LOG_TRIVIAL(error) << "http_error: status=" << status << ", code=" << code << ", error=" << error;
result = wxString::Format("code=%u, error=%s", code, from_u8(error));
return result;
} catch(...) {
;
}
return wxEmptyString;
}
void PrintJob::process()
{
/* display info */
wxString msg;
wxString error_str;
int curr_percent = 10;
NetworkAgent* m_agent = wxGetApp().getAgent();
AppConfig* config = wxGetApp().app_config;
if (this->connection_type == "lan") {
msg = _L("Sending print job over LAN");
}
else {
msg = _L("Sending print job through cloud service");
}
int result = -1;
unsigned int http_code;
std::string http_body;
int total_plate_num = plate_data.plate_count;
if (!plate_data.is_valid) {
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() && m_print_type == "from_normal") {
update_status(curr_percent, check_gcode_failed_str);
return;
}
if (was_canceled()) {
update_status(curr_percent, printjob_cancel_str);
return;
}
}
m_project_name = truncate_string(m_project_name, 100);
int curr_plate_idx = 0;
if (m_print_type == "from_normal") {
if (plate_data.is_valid)
curr_plate_idx = plate_data.cur_plate_index;
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;
else if (job_data.plate_idx == PLATE_ALL_IDX)
curr_plate_idx = m_plater->get_partplate_list().get_curr_plate_index() + 1;
else
curr_plate_idx = m_plater->get_partplate_list().get_curr_plate_index() + 1;
}
else if(m_print_type == "from_sdcard_view") {
curr_plate_idx = m_print_from_sdc_plate_idx;
}
PartPlate* curr_plate = m_plater->get_partplate_list().get_curr_plate();
if (curr_plate) {
this->task_bed_type = bed_type_to_gcode_string(plate_data.is_valid ? plate_data.bed_type : curr_plate->get_bed_type(true));
}
QDT::PrintParams params;
// local print access
params.dev_ip = m_dev_ip;
params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
params.use_ssl_for_mqtt = m_local_use_ssl_for_mqtt;
params.username = "qdtp";
params.password = m_access_code;
// check access code and ip address
if (this->connection_type == "lan" && m_print_type == "from_normal") {
params.dev_id = m_dev_id;
params.project_name = "verify_job";
params.filename = job_data._temp_path.string();
params.connection_type = this->connection_type;
result = m_agent->start_send_gcode_to_sdcard(params, nullptr, nullptr, nullptr);
if (result != 0) {
BOOST_LOG_TRIVIAL(error) << "access code is invalid";
m_enter_ip_address_fun_fail();
m_job_finished = true;
return;
}
params.project_name = "";
params.filename = "";
}
params.dev_id = m_dev_id;
params.ftp_folder = m_ftp_folder;
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.ams_mapping_info = this->task_ams_mapping_info;
params.connection_type = this->connection_type;
params.task_use_ams = this->task_use_ams;
params.task_bed_type = this->task_bed_type;
params.print_type = this->m_print_type;
if (m_print_type == "from_sdcard_view") {
params.dst_file = m_dst_path;
}
if (wxGetApp().model().model_info && wxGetApp().model().model_info.get()) {
ModelInfo* model_info = wxGetApp().model().model_info.get();
auto origin_profile_id = model_info->metadata_items.find(QDT_DESIGNER_PROFILE_ID_TAG);
if (origin_profile_id != model_info->metadata_items.end()) {
try {
params.origin_profile_id = stoi(origin_profile_id->second.c_str());
}
catch(...) {}
}
auto origin_model_id = model_info->metadata_items.find(QDT_DESIGNER_MODEL_ID_TAG);
if (origin_model_id != model_info->metadata_items.end()) {
try {
params.origin_model_id = origin_model_id->second;
}
catch(...) {}
}
auto profile_name = model_info->metadata_items.find(QDT_DESIGNER_PROFILE_TITLE_TAG);
if (profile_name != model_info->metadata_items.end()) {
try {
params.preset_name = profile_name->second;
}
catch (...) {}
}
auto model_name = model_info->metadata_items.find(QDT_DESIGNER_MODEL_TITLE_TAG);
if (model_name != model_info->metadata_items.end()) {
try {
std::string mall_model_name = model_name->second;
std::replace(mall_model_name.begin(), mall_model_name.end(), ' ', '_');
const char* unusable_symbols = "<>[]:/\\|?*\" ";
for (const char* symbol = unusable_symbols; *symbol != '\0'; ++symbol) {
std::replace(mall_model_name.begin(), mall_model_name.end(), *symbol, '_');
}
std::regex pattern("_+");
params.project_name = std::regex_replace(mall_model_name, pattern, "_");
}
catch (...) {}
}
}
params.stl_design_id = 0;
if (!wxGetApp().model().stl_design_id.empty()) {
auto country_code = wxGetApp().app_config->get_country_code();
bool match_code = false;
if (wxGetApp().model().stl_design_country == "DEV" && (country_code == "ENV_CN_DEV" || country_code == "NEW_ENV_DEV_HOST")) {
match_code = true;
}
if (wxGetApp().model().stl_design_country == "QA" && (country_code == "ENV_CN_QA" || country_code == "NEW_ENV_QAT_HOST")) {
match_code = true;
}
if (wxGetApp().model().stl_design_country == "CN_PRE" && (country_code == "ENV_CN_PRE" || country_code == "NEW_ENV_PRE_HOST")) {
match_code = true;
}
if (wxGetApp().model().stl_design_country == "US_PRE" && country_code == "ENV_US_PRE") {
match_code = true;
}
if (country_code == wxGetApp().model().stl_design_country) {
match_code = true;
}
if (match_code) {
int stl_design_id = 0;
try {
stl_design_id = std::stoi(wxGetApp().model().stl_design_id);
}
catch (...) {
stl_design_id = 0;
}
params.stl_design_id = stl_design_id;
}
}
if (params.stl_design_id == 0 || !wxGetApp().model().design_id.empty()) {
try {
params.stl_design_id = std::stoi(wxGetApp().model().design_id);
}
catch (...)
{
params.stl_design_id = 0;
}
}
if (params.preset_name.empty() && m_print_type == "from_normal") { params.preset_name = wxString::Format("%s_plate_%d", m_project_name, curr_plate_idx).ToStdString(); }
if (params.project_name.empty()) {params.project_name = m_project_name;}
if (m_is_calibration_task) {
params.project_name = m_project_name;
params.origin_model_id = "";
}
wxString error_text;
wxString msg_text;
const int StagePercentPoint[(int)PrintingStageFinished + 1] = {
20, // PrintingStageCreate
30, // PrintingStageUpload
70, // PrintingStageWaiting
75, // PrintingStageRecord
97, // PrintingStageSending
100, // PrintingStageFinished
100 // PrintingStageFinished
};
bool is_try_lan_mode = false;
bool is_try_lan_mode_failed = false;
auto update_fn = [this,
&is_try_lan_mode,
&is_try_lan_mode_failed,
&msg,
&error_str,
&curr_percent,
&error_text,
StagePercentPoint
](int stage, int code, std::string info) {
if (stage == QDT::SendingPrintJobStage::PrintingStageCreate && !is_try_lan_mode_failed) {
if (this->connection_type == "lan") {
msg = _L("Sending print job over LAN");
} else {
msg = _L("Sending print job through cloud service");
}
}
else if (stage == QDT::SendingPrintJobStage::PrintingStageUpload && !is_try_lan_mode_failed) {
if (code >= 0 && code <= 100 && !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 == QDT::SendingPrintJobStage::PrintingStageWaiting) {
if (this->connection_type == "lan") {
msg = _L("Sending print job over LAN");
} else {
msg = _L("Sending print job through cloud service");
}
}
else if (stage == QDT::SendingPrintJobStage::PrintingStageRecord && !is_try_lan_mode) {
msg = _L("Sending print configuration");
}
else if (stage == QDT::SendingPrintJobStage::PrintingStageSending && !is_try_lan_mode) {
if (this->connection_type == "lan") {
msg = _L("Sending print job over LAN");
} else {
msg = _L("Sending print job through cloud service");
}
}
else if (stage == QDT::SendingPrintJobStage::PrintingStageFinished) {
msg = wxString::Format(_L("Successfully sent. Will automatically jump to the device page in %ss"), info);
if (m_print_job_completed_id == wxGetApp().plater()->get_send_calibration_finished_event()) {
msg = wxString::Format(_L("Successfully sent. Will automatically jump to the next page in %ss"), info);
}
this->update_percent_finish();
} else {
if (this->connection_type == "lan") {
msg = _L("Sending print job over LAN");
} else {
msg = _L("Sending print job through cloud service");
}
}
// update current percnet
if (stage >= 0 && stage <= (int) PrintingStageFinished) {
curr_percent = StagePercentPoint[stage];
if ((stage == QDT::SendingPrintJobStage::PrintingStageUpload
|| stage == QDT::SendingPrintJobStage::PrintingStageRecord)
&& (code > 0 && code <= 100)) {
curr_percent = (StagePercentPoint[stage + 1] - StagePercentPoint[stage]) * code / 100 + StagePercentPoint[stage];
}
}
//get errors
if (code > 100 || code < 0 || stage == QDT::SendingPrintJobStage::PrintingStageERROR) {
if (code == QIDI_NETWORK_ERR_PRINT_WR_FILE_OVER_SIZE || code == QIDI_NETWORK_ERR_PRINT_SP_FILE_OVER_SIZE) {
m_plater->update_print_error_info(code, desc_file_too_large.ToStdString(), info);
}else if (code == QIDI_NETWORK_ERR_PRINT_WR_FILE_NOT_EXIST || code == QIDI_NETWORK_ERR_PRINT_SP_FILE_NOT_EXIST){
m_plater->update_print_error_info(code, desc_fail_not_exist.ToStdString(), info);
}else if (code == QIDI_NETWORK_ERR_PRINT_LP_UPLOAD_FTP_FAILED || code == QIDI_NETWORK_ERR_PRINT_SG_UPLOAD_FTP_FAILED) {
m_plater->update_print_error_info(code, desc_upload_ftp_failed.ToStdString(), info);
}else {
m_plater->update_print_error_info(code, desc_network_error.ToStdString(), info);
}
}
else {
this->update_status(curr_percent, msg);
}
};
auto cancel_fn = [this]() {
return was_canceled();
};
DeviceManager* dev = wxGetApp().getDeviceManager();
MachineObject* obj = dev->get_selected_machine();
auto wait_fn = [this, curr_percent, &obj](int state, std::string job_info) {
BOOST_LOG_TRIVIAL(info) << "print_job: get_job_info = " << job_info;
if (!obj->is_support_wait_sending_finish) {
return true;
}
std::string curr_job_id;
json job_info_j;
try {
job_info_j.parse(job_info);
if (job_info_j.contains("job_id")) {
curr_job_id = job_info_j["job_id"].get<std::string>();
}
BOOST_LOG_TRIVIAL(trace) << "print_job: curr_obj_id=" << curr_job_id;
} catch(...) {
;
}
if (obj) {
int time_out = 0;
while (time_out < PRINT_JOB_SENDING_TIMEOUT) {
BOOST_LOG_TRIVIAL(trace) << "print_job: obj job_id = " << obj->job_id_;
if (!obj->job_id_.empty() && obj->job_id_.compare(curr_job_id) == 0) {
BOOST_LOG_TRIVIAL(info) << "print_job: got job_id = " << obj->job_id_ << ", time_out=" << time_out;
return true;
}
if (obj->is_in_printing_status(obj->print_status)) {
BOOST_LOG_TRIVIAL(info) << "print_job: printer has enter printing status, s = " << obj->print_status;
return true;
}
time_out++;
boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
}
//this->update_status(curr_percent, _L("Print task sending times out."));
//m_plater->update_print_error_info(QIDI_NETWORK_ERR_TIMEOUT, wait_sending_finish.ToStdString(), desc_wait_sending_finish.ToStdString());
BOOST_LOG_TRIVIAL(info) << "print_job: timeout, cancel the job" << obj->job_id_;
/* handle tiemout */
//obj->command_task_cancel(curr_job_id);
//return false;
return true;
}
BOOST_LOG_TRIVIAL(info) << "print_job: obj is null";
return true;
};
if (params.connection_type != "lan") {
if (params.dev_ip.empty())
params.comments = "no_ip";
else if (this->cloud_print_only)
params.comments = "low_version";
else if (!this->has_sdcard)
params.comments = "no_sdcard";
else if (params.password.empty())
params.comments = "no_password";
//use ftp only
if (m_print_type == "from_sdcard_view") {
BOOST_LOG_TRIVIAL(info) << "print_job: try to send with cloud, model is sdcard view";
this->update_status(curr_percent, _L("Sending print job through cloud service"));
result = m_agent->start_sdcard_print(params, update_fn, cancel_fn);
}
else if (!wxGetApp().app_config->get("lan_mode_only").empty() && wxGetApp().app_config->get("lan_mode_only") == "1") {
if (params.password.empty() || params.dev_ip.empty()) {
error_text = wxString::Format("Access code:%s Ip address:%s", params.password, params.dev_ip);
result = QIDI_NETWORK_ERR_FTP_UPLOAD_FAILED;
}
else {
BOOST_LOG_TRIVIAL(info) << "print_job: use ftp send print only";
this->update_status(curr_percent, _L("Sending print job over LAN"));
is_try_lan_mode = true;
result = m_agent->start_local_print_with_record(params, update_fn, cancel_fn, wait_fn);
if (result < 0) {
error_text = wxString::Format("Access code:%s Ip address:%s", params.password, params.dev_ip);
// try to send with cloud
BOOST_LOG_TRIVIAL(warning) << "print_job: use ftp send print failed";
}
}
}
else {
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, wait_fn);
if (result == 0) {
params.comments = "";
}
else if (result == QIDI_NETWORK_ERR_PRINT_WR_UPLOAD_FTP_FAILED) {
params.comments = "upload_failed";
}
else {
params.comments = (boost::format("failed(%1%)") % result).str();
}
if (result < 0) {
is_try_lan_mode_failed = true;
// 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, wait_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, wait_fn);
}
}
} 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 (result < 0) {
curr_percent = -1;
if (result == QIDI_NETWORK_ERR_PRINT_WR_FILE_NOT_EXIST || result == QIDI_NETWORK_ERR_PRINT_SP_FILE_NOT_EXIST) {
msg_text = file_is_not_exists_str;
} else if (result == QIDI_NETWORK_ERR_PRINT_SP_FILE_OVER_SIZE || result == QIDI_NETWORK_ERR_PRINT_WR_FILE_OVER_SIZE) {
msg_text = file_over_size_str;
} else if (result == QIDI_NETWORK_ERR_PRINT_WR_CHECK_MD5_FAILED || result == QIDI_NETWORK_ERR_PRINT_SP_CHECK_MD5_FAILED) {
msg_text = failed_in_cloud_service_str;
} else if (result == QIDI_NETWORK_ERR_PRINT_WR_GET_NOTIFICATION_TIMEOUT || result == QIDI_NETWORK_ERR_PRINT_SP_GET_NOTIFICATION_TIMEOUT) {
msg_text = timeout_to_upload_str;
} else if (result == QIDI_NETWORK_ERR_PRINT_LP_UPLOAD_FTP_FAILED || result == QIDI_NETWORK_ERR_PRINT_SG_UPLOAD_FTP_FAILED) {
msg_text = upload_ftp_failed_str;
} else if (result == QIDI_NETWORK_ERR_CANCELED) {
msg_text = print_canceled_str;
this->update_status(0, msg_text);
} else {
msg_text = send_print_failed_str;
}
if (result != QIDI_NETWORK_ERR_CANCELED) {
this->show_error_info(msg_text, 0, "", "");
}
BOOST_LOG_TRIVIAL(error) << "print_job: failed, result = " << result;
} else {
// wait for printer mqtt ready the same job id
wxGetApp().plater()->record_slice_preset("print");
BOOST_LOG_TRIVIAL(error) << "print_job: send ok.";
wxCommandEvent* evt = new wxCommandEvent(m_print_job_completed_id);
if (!m_completed_evt_data.empty())
evt->SetString(m_completed_evt_data);
else
evt->SetString(m_dev_id);
if (m_print_job_completed_id == wxGetApp().plater()->get_send_calibration_finished_event()) {
int sel = wxGetApp().mainframe->get_calibration_curr_tab();
if (sel >= 0) {
evt->SetInt(sel);
}
}
wxQueueEvent(m_plater, evt);
m_job_finished = true;
}
}
void PrintJob::finalize() {
if (was_canceled()) return;
Job::finalize();
}
void PrintJob::set_project_name(std::string name)
{
m_project_name = name;
}
void PrintJob::set_dst_name(std::string path)
{
m_dst_path = path;
}
void PrintJob::on_check_ip_address_fail(std::function<void()> func)
{
m_enter_ip_address_fun_fail = func;
}
void PrintJob::on_check_ip_address_success(std::function<void()> func)
{
m_enter_ip_address_fun_success = func;
}
void PrintJob::connect_to_local_mqtt()
{
this->update_status(0, wxEmptyString);
}
void PrintJob::set_calibration_task(bool is_calibration)
{
m_is_calibration_task = is_calibration;
}
}} // namespace Slic3r::GUI

View File

@@ -0,0 +1,120 @@
#ifndef PrintJOB_HPP
#define PrintJOB_HPP
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include "libslic3r/PrintConfig.hpp"
#include "PlaterJob.hpp"
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
#define PRINT_JOB_SENDING_TIMEOUT 25
class PrintPrepareData
{
public:
bool is_from_plater = true;
int plate_idx;
fs::path _3mf_path;
fs::path _3mf_config_path;
fs::path _temp_path;
PrintPrepareData() {
plate_idx = 0;
}
};
class PlateListData
{
public:
bool is_valid = false;
int plate_count = 0;
int cur_plate_index = 0;
BedType bed_type = BedType::btDefault;
};
class PrintJob : public PlaterJob
{
std::function<void()> m_success_fun{nullptr};
std::string m_dev_id;
bool m_job_finished{ false };
int m_print_job_completed_id = 0;
wxString m_completed_evt_data;
std::function<void()> m_enter_ip_address_fun_fail{ nullptr };
std::function<void()> m_enter_ip_address_fun_success{ nullptr };
public:
PrintPrepareData job_data;
PlateListData plate_data;
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_project_name;
std::string m_dev_ip;
std::string m_ftp_folder;
std::string m_access_code;
std::string task_bed_type;
std::string task_ams_mapping;
std::string task_ams_mapping_info;
std::string connection_type;
std::string m_print_type;
std::string m_dst_path;
bool m_is_calibration_task = false;
int m_print_from_sdc_plate_idx = 0;
bool m_local_use_ssl_for_mqtt { true };
bool m_local_use_ssl_for_ftp { true };
bool task_bed_leveling;
bool task_flow_cali;
bool task_vibration_cali;
bool task_record_timelapse;
bool task_layer_inspect;
bool cloud_print_only { false };
bool has_sdcard { false };
bool task_use_ams { true };
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, wxString evt_data = wxEmptyString) {
m_print_job_completed_id = event_id;
m_completed_evt_data = evt_data;
}
void on_success(std::function<void()> success);
void process() override;
void finalize() override;
void set_project_name(std::string name);
void set_dst_name(std::string path);
void on_check_ip_address_fail(std::function<void()> func);
void on_check_ip_address_success(std::function<void()> func);
void connect_to_local_mqtt();
wxString get_http_error_msg(unsigned int status, std::string body);
std::string truncate_string(const std::string& str, size_t maxLength);
void set_calibration_task(bool is_calibration);
};
}} // namespace Slic3r::GUI
#endif

View File

@@ -0,0 +1,32 @@
#ifndef IPROGRESSINDICATOR_HPP
#define IPROGRESSINDICATOR_HPP
#include <string>
#include <functional>
#include <wx/string.h>
namespace Slic3r {
/**
* @brief Generic progress indication interface.
*/
class ProgressIndicator {
public:
/// Cancel callback function type
using CancelFn = std::function<void()>;
virtual ~ProgressIndicator() = default;
virtual void clear_percent() = 0;
virtual void show_error_info(wxString msg, int code, wxString description, wxString extra) = 0;
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

View File

@@ -0,0 +1,418 @@
#include "SendJob.hpp"
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.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 status 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 send_print_failed_str = _L("Failed to send the print job. Please try again.");
static wxString upload_ftp_failed_str = _L("Failed to upload file to ftp. Please try again.");
static wxString desc_network_error = _L("Check the current status of the qidi server by clicking on the link above.");
static wxString desc_file_too_large = _L("The size of the print file is too large. Please adjust the file size and try again.");
static wxString desc_fail_not_exist = _L("Print file not found, Please slice it again and send it for printing.");
static wxString desc_upload_ftp_failed = _L("Failed to upload print file to FTP. Please check the network status and try again.");
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");
SendJob::SendJob(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_send_finished_event();
}
void SendJob::prepare()
{
m_plater->get_print_job_data(&job_data);
if (&job_data) {
std::string temp_file = Slic3r::resources_dir() + "/check_access_code.txt";
auto check_access_code_path = temp_file.c_str();
BOOST_LOG_TRIVIAL(trace) << "sned_job: check_access_code_path = " << check_access_code_path;
job_data._temp_path = fs::path(check_access_code_path);
}
}
void SendJob::on_exception(const std::exception_ptr &eptr)
{
try {
if (eptr)
std::rethrow_exception(eptr);
} catch (std::exception &e) {
PlaterJob::on_exception(eptr);
}
}
wxString SendJob::get_http_error_msg(unsigned int status, std::string body)
{
int code = 0;
std::string error;
std::string message;
wxString result;
if (status >= 400 && status < 500)
try {
json j = json::parse(body);
if (j.contains("code")) {
if (!j["code"].is_null())
code = j["code"].get<int>();
}
if (j.contains("error")) {
if (!j["error"].is_null())
error = j["error"].get<std::string>();
}
if (j.contains("message")) {
if (!j["message"].is_null())
message = j["message"].get<std::string>();
}
switch (status) {
;
}
}
catch (...) {
;
}
else if (status == 503) {
return _L("Service Unavailable");
}
else {
wxString unkown_text = _L("Unknown Error.");
unkown_text += wxString::Format("status=%u, body=%s", status, body);
return unkown_text;
}
BOOST_LOG_TRIVIAL(error) << "http_error: status=" << status << ", code=" << code << ", error=" << error;
result = wxString::Format("code=%u, error=%s", code, from_u8(error));
return result;
}
inline std::string get_transform_string(int bytes)
{
float ms = (float)bytes / 1024.0f / 1024.0f;
float ks = (float)bytes / 1024.0f;
char buffer[32];
if (ms > 0)
::sprintf(buffer, "%.1fM", ms);
else if (ks > 0)
::sprintf(buffer, "%.1fK", ks);
else
::sprintf(buffer, "%.1fK", ks);
return buffer;
}
void SendJob::process()
{
QDT::PrintParams params;
wxString msg;
int curr_percent = 10;
NetworkAgent* m_agent = wxGetApp().getAgent();
AppConfig* config = wxGetApp().app_config;
int result = -1;
unsigned int http_code;
std::string http_body;
// local print access
params.dev_ip = m_dev_ip;
params.username = "qdtp";
params.password = m_access_code;
params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
params.use_ssl_for_mqtt = m_local_use_ssl_for_mqtt;
// check access code and ip address
params.dev_id = m_dev_id;
params.project_name = "verify_job";
params.filename = job_data._temp_path.string();
params.connection_type = this->connection_type;
result = m_agent->start_send_gcode_to_sdcard(params, nullptr, nullptr, nullptr);
if (result != 0) {
BOOST_LOG_TRIVIAL(error) << "access code is invalid";
m_enter_ip_address_fun_fail(result);
m_job_finished = true;
return;
}
else if(m_is_check_mode && !m_check_and_continue){
m_enter_ip_address_fun_success();
m_job_finished = true;
return;
}
/* display info */
msg = _L("Sending gcode file over LAN");
/* if (this->connection_type == "lan") {
msg = _L("Sending gcode file over LAN");
}
else {
msg = _L("Sending gcode file through cloud service");
}*/
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) {
if (job_data.plate_idx == PLATE_ALL_IDX) {
//all plate
for (int index = 0; index < total_plate_num; index++)
{
PartPlate* plate_n = m_plater->get_partplate_list().get_plate(index);
if (plate_n && plate_n->is_valid_gcode_file())
{
plate = plate_n;
break;
}
}
}
else {
plate = m_plater->get_partplate_list().get_curr_plate();
}
if (plate == nullptr) {
BOOST_LOG_TRIVIAL(error) << "can not find plate with valid gcode file when sending to print, plate_index="<< job_data.plate_idx;
update_status(curr_percent, check_gcode_failed_str);
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;
params.dev_id = m_dev_id;
params.project_name = m_project_name + ".gcode.3mf";
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.ams_mapping = this->task_ams_mapping;
params.connection_type = this->connection_type;
params.task_use_ams = this->task_use_ams;
// local print access
params.dev_ip = m_dev_ip;
params.username = "qdtp";
params.password = m_access_code;
params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
params.use_ssl_for_mqtt = m_local_use_ssl_for_mqtt;
wxString error_text;
wxString msg_text;
const int StagePercentPoint[(int)PrintingStageFinished + 1] = {
20, // PrintingStageCreate
30, // PrintingStageUpload
99, // PrintingStageWaiting
99, // PrintingStageRecord
99, // PrintingStageSending
100, // PrintingStageFinished
100 // PrintingStageFinished
};
auto update_fn = [this, &msg, &curr_percent, &error_text, StagePercentPoint](int stage, int code, std::string info) {
if (stage == SendingPrintJobStage::PrintingStageCreate) {
if (this->connection_type == "lan") {
msg = _L("Sending gcode file over LAN");
} else {
msg = _L("Sending gcode file to sdcard");
}
}
else if (stage == SendingPrintJobStage::PrintingStageUpload) {
if (code >= 0 && code <= 100 && !info.empty()) {
if (this->connection_type == "lan") {
msg = _L("Sending gcode file over LAN");
}
else {
msg = _L("Sending gcode file to sdcard");
}
if (!info.empty()) {
msg += wxString::Format("(%s)", info);
}
}
}
else if (stage == SendingPrintJobStage::PrintingStageFinished) {
msg = wxString::Format(_L("Successfully sent. Close current page in %s s"), info);
}
else {
if (this->connection_type == "lan") {
msg = _L("Sending gcode file over LAN");
}
else {
msg = _L("Sending gcode file over LAN");
}
}
// update current percnet
if (stage >= 0 && stage <= (int) PrintingStageFinished) {
curr_percent = StagePercentPoint[stage];
if ((stage == QDT::SendingPrintJobStage::PrintingStageUpload) &&
(code > 0 && code <= 100)) {
curr_percent = (StagePercentPoint[stage + 1] - StagePercentPoint[stage]) * code / 100 + StagePercentPoint[stage];
}
}
//get errors
if (code > 100 || code < 0 || stage == QDT::SendingPrintJobStage::PrintingStageERROR) {
if (code == QIDI_NETWORK_ERR_PRINT_WR_FILE_OVER_SIZE || code == QIDI_NETWORK_ERR_PRINT_SP_FILE_OVER_SIZE) {
m_plater->update_print_error_info(code, desc_file_too_large.ToStdString(), info);
}
else if (code == QIDI_NETWORK_ERR_PRINT_WR_FILE_NOT_EXIST || code == QIDI_NETWORK_ERR_PRINT_SP_FILE_NOT_EXIST) {
m_plater->update_print_error_info(code, desc_fail_not_exist.ToStdString(), info);
}
else if (code == QIDI_NETWORK_ERR_PRINT_LP_UPLOAD_FTP_FAILED || code == QIDI_NETWORK_ERR_PRINT_SG_UPLOAD_FTP_FAILED) {
m_plater->update_print_error_info(code, desc_upload_ftp_failed.ToStdString(), info);
}
else {
m_plater->update_print_error_info(code, desc_network_error.ToStdString(), info);
}
}
else {
this->update_status(curr_percent, msg);
}
};
auto cancel_fn = [this]() {
return was_canceled();
};
if (params.connection_type != "lan") {
if (params.dev_ip.empty())
params.comments = "no_ip";
else if (this->cloud_print_only)
params.comments = "low_version";
else if (!this->has_sdcard)
params.comments = "no_sdcard";
else if (params.password.empty())
params.comments = "no_password";
if (!params.password.empty()
&& !params.dev_ip.empty()
&& this->has_sdcard) {
// try to send local with record
BOOST_LOG_TRIVIAL(info) << "send_job: try to send gcode to printer";
this->update_status(curr_percent, _L("Sending gcode file over LAN"));
result = m_agent->start_send_gcode_to_sdcard(params, update_fn, cancel_fn, nullptr);
if (result == QIDI_NETWORK_ERR_FTP_UPLOAD_FAILED) {
params.comments = "upload_failed";
} else {
params.comments = (boost::format("failed(%1%)") % result).str();
}
if (result < 0) {
// try to send with cloud
BOOST_LOG_TRIVIAL(info) << "send_job: try to send gcode file to printer";
this->update_status(curr_percent, _L("Sending gcode file over LAN"));
}
} else {
BOOST_LOG_TRIVIAL(info) << "send_job: try to send gcode file to printer";
this->update_status(curr_percent, _L("Sending gcode file over LAN"));
}
} else {
if (this->has_sdcard) {
this->update_status(curr_percent, _L("Sending gcode file over LAN"));
result = m_agent->start_send_gcode_to_sdcard(params, update_fn, cancel_fn, nullptr);
} else {
this->update_status(curr_percent, _L("An SD card needs to be inserted before sending to printer."));
return;
}
}
if (was_canceled()) {
update_status(curr_percent, printjob_cancel_str);
return;
}
if (result < 0) {
curr_percent = -1;
if (result == QIDI_NETWORK_ERR_PRINT_WR_FILE_NOT_EXIST || result == QIDI_NETWORK_ERR_PRINT_SP_FILE_NOT_EXIST) {
msg_text = file_is_not_exists_str;
}
else if (result == QIDI_NETWORK_ERR_PRINT_SP_FILE_OVER_SIZE || result == QIDI_NETWORK_ERR_PRINT_WR_FILE_OVER_SIZE) {
msg_text = file_over_size_str;
}
else if (result == QIDI_NETWORK_ERR_PRINT_WR_CHECK_MD5_FAILED || result == QIDI_NETWORK_ERR_PRINT_SP_CHECK_MD5_FAILED) {
msg_text = failed_in_cloud_service_str;
}
else if (result == QIDI_NETWORK_ERR_PRINT_WR_GET_NOTIFICATION_TIMEOUT || result == QIDI_NETWORK_ERR_PRINT_SP_GET_NOTIFICATION_TIMEOUT) {
msg_text = timeout_to_upload_str;
}
else if (result == QIDI_NETWORK_ERR_PRINT_LP_UPLOAD_FTP_FAILED || result == QIDI_NETWORK_ERR_PRINT_SG_UPLOAD_FTP_FAILED) {
msg_text = upload_ftp_failed_str;
}
else if (result == QIDI_NETWORK_ERR_CANCELED) {
msg_text = print_canceled_str;
}
else {
msg_text = send_print_failed_str;
}
if (result != QIDI_NETWORK_ERR_CANCELED) {
this->show_error_info(msg_text, 0, "", "");
}
BOOST_LOG_TRIVIAL(error) << "send_job: failed, result = " << result;
}
else {
BOOST_LOG_TRIVIAL(error) << "send_job: send ok.";
wxCommandEvent* evt = new wxCommandEvent(m_print_job_completed_id);
evt->SetString(from_u8(params.project_name));
wxQueueEvent(m_plater, evt);
m_job_finished = true;
}
}
void SendJob::on_success(std::function<void()> success)
{
m_success_fun = success;
}
void SendJob::on_check_ip_address_fail(std::function<void(int)> func)
{
m_enter_ip_address_fun_fail = func;
}
void SendJob::on_check_ip_address_success(std::function<void()> func)
{
m_enter_ip_address_fun_success = func;
}
void SendJob::finalize() {
if (was_canceled()) return;
Job::finalize();
}
void SendJob::set_project_name(std::string name)
{
m_project_name = name;
}
}} // namespace Slic3r::GUI

View File

@@ -0,0 +1,71 @@
#ifndef SendJOB_HPP
#define SendJOB_HPP
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include "PlaterJob.hpp"
#include "PrintJob.hpp"
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
typedef std::function<void(int status, int code, std::string msg)> OnUpdateStatusFn;
typedef std::function<bool()> WasCancelledFn;
class SendJob : public PlaterJob
{
PrintPrepareData job_data;
std::string m_dev_id;
bool m_job_finished{ false };
int m_print_job_completed_id = 0;
bool m_is_check_mode{false};
bool m_check_and_continue{false};
std::function<void()> m_success_fun{nullptr};
std::function<void(int)> m_enter_ip_address_fun_fail{nullptr};
std::function<void()> m_enter_ip_address_fun_success{nullptr};
protected:
void prepare() override;
void on_exception(const std::exception_ptr &) override;
public:
SendJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater, std::string dev_id = "");
std::string m_project_name;
std::string m_dev_ip;
std::string m_access_code;
std::string task_bed_type;
std::string task_ams_mapping;
std::string connection_type;
bool m_local_use_ssl_for_ftp{true};
bool m_local_use_ssl_for_mqtt{true};
bool cloud_print_only { false };
bool has_sdcard { false };
bool task_use_ams { true };
wxWindow* m_parent{nullptr};
int status_range() const override
{
return 100;
}
wxString get_http_error_msg(unsigned int status, std::string body);
void set_check_mode() {m_is_check_mode = true;};
void check_and_continue() {m_check_and_continue = true;};
bool is_finished() { return m_job_finished; }
void process() override;
void on_success(std::function<void()> success);
void on_check_ip_address_fail(std::function<void(int)> func);
void on_check_ip_address_success(std::function<void()> func);
void finalize() override;
void set_project_name(std::string name);
};
}}
#endif

View File

@@ -0,0 +1,144 @@
#include "UpgradeNetworkJob.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/Utils/Http.hpp"
namespace Slic3r {
namespace GUI {
wxDEFINE_EVENT(EVT_UPGRADE_UPDATE_MESSAGE, wxCommandEvent);
wxDEFINE_EVENT(EVT_UPGRADE_NETWORK_SUCCESS, wxCommandEvent);
wxDEFINE_EVENT(EVT_DOWNLOAD_NETWORK_FAILED, wxCommandEvent);
wxDEFINE_EVENT(EVT_INSTALL_NETWORK_FAILED, wxCommandEvent);
UpgradeNetworkJob::UpgradeNetworkJob(std::shared_ptr<ProgressIndicator> pri)
: Job{std::move(pri)}
{
name = "plugins";
package_name = "networking_plugins.zip";
}
void UpgradeNetworkJob::on_exception(const std::exception_ptr &eptr)
{
try {
if (eptr)
std::rethrow_exception(eptr);
} catch (std::exception &e) {
UpgradeNetworkJob::on_exception(eptr);
}
}
void UpgradeNetworkJob::on_success(std::function<void()> success)
{
m_success_fun = success;
}
void UpgradeNetworkJob::update_status(int st, const wxString &msg)
{
BOOST_LOG_TRIVIAL(info) << "UpgradeNetworkJob: percent = " << st << "msg = " << msg;
GUI::Job::update_status(st, msg);
wxCommandEvent event(EVT_UPGRADE_UPDATE_MESSAGE);
event.SetString(msg);
event.SetEventObject(m_event_handle);
wxPostEvent(m_event_handle, event);
}
void UpgradeNetworkJob::process()
{
// downloading
int result = 0;
AppConfig* app_config = wxGetApp().app_config;
if (!app_config)
return;
BOOST_LOG_TRIVIAL(info) << "[UpgradeNetworkJob process]: enter";
// get temp path
fs::path target_file_path = (fs::temp_directory_path() / package_name);
fs::path tmp_path = target_file_path;
auto path_str = tmp_path.string() + wxString::Format(".%d%s", get_current_pid(), ".tmp").ToStdString();
tmp_path = fs::path(path_str);
BOOST_LOG_TRIVIAL(info) << "UpgradeNetworkJob: save netowrk_plugin to " << tmp_path.string();
auto cancel_fn = [this]() {
return was_canceled();
};
int curr_percent = 0;
result = wxGetApp().download_plugin(name, package_name,
[this, &curr_percent](int state, int percent, bool &cancel) {
if (state == InstallStatusNormal) {
update_status(percent, _L("Downloading"));
} else if (state == InstallStatusDownloadFailed) {
update_status(percent, _L("Download failed"));
} else {
update_status(percent, _L("Downloading"));
}
curr_percent = percent;
}, cancel_fn);
if (was_canceled()) {
update_status(0, _L("Cancelled"));
wxCommandEvent event(wxEVT_CLOSE_WINDOW);
event.SetEventObject(m_event_handle);
wxPostEvent(m_event_handle, event);
return;
}
if (result < 0) {
update_status(0, _L("Download failed"));
wxCommandEvent event(EVT_DOWNLOAD_NETWORK_FAILED);
event.SetEventObject(m_event_handle);
wxPostEvent(m_event_handle, event);
return;
}
result = wxGetApp().install_plugin(
name, package_name,
[this](int state, int percent, bool &cancel) {
if (state == InstallStatusInstallCompleted) {
update_status(percent, _L("Install successfully."));
} else {
update_status(percent, _L("Installing"));
}
}, cancel_fn);
if (was_canceled()) {
update_status(0, _L("Cancelled"));
wxCommandEvent event(wxEVT_CLOSE_WINDOW);
event.SetEventObject(m_event_handle);
wxPostEvent(m_event_handle, event);
return;
}
if (result != 0) {
update_status(0, _L("Install failed"));
wxCommandEvent event(EVT_INSTALL_NETWORK_FAILED);
event.SetEventObject(m_event_handle);
wxPostEvent(m_event_handle, event);
return;
}
wxCommandEvent event(EVT_UPGRADE_NETWORK_SUCCESS);
event.SetEventObject(m_event_handle);
wxPostEvent(m_event_handle, event);
BOOST_LOG_TRIVIAL(info) << "[UpgradeNetworkJob process]: exit";
return;
}
void UpgradeNetworkJob::finalize()
{
if (was_canceled()) return;
Job::finalize();
}
void UpgradeNetworkJob::set_event_handle(wxWindow *hanle)
{
m_event_handle = hanle;
}
}} // namespace Slic3r::GUI

View File

@@ -0,0 +1,62 @@
#ifndef __UpgradeNetworkJob_HPP__
#define __UpgradeNetworkJob_HPP__
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#include <functional>
#include "Job.hpp"
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
enum PluginInstallStatus {
InstallStatusNormal = 0,
InstallStatusDownloadFailed = 1,
InstallStatusDownloadCompleted = 2,
InstallStatusUnzipFailed = 3,
InstallStatusInstallCompleted = 4,
};
typedef std::function<void(int status, int percent, bool& cancel)> InstallProgressFn;
class UpgradeNetworkJob : public Job
{
wxWindow * m_event_handle{nullptr};
std::function<void()> m_success_fun{nullptr};
bool m_job_finished{ false };
int m_print_job_completed_id = 0;
InstallProgressFn pro_fn { nullptr };
protected:
std::string name;
std::string package_name;
void on_exception(const std::exception_ptr &) override;
public:
UpgradeNetworkJob(std::shared_ptr<ProgressIndicator> pri);
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);
};
wxDECLARE_EVENT(EVT_UPGRADE_UPDATE_MESSAGE, wxCommandEvent);
wxDECLARE_EVENT(EVT_UPGRADE_NETWORK_SUCCESS, wxCommandEvent);
wxDECLARE_EVENT(EVT_DOWNLOAD_NETWORK_FAILED, wxCommandEvent);
wxDECLARE_EVENT(EVT_INSTALL_NETWORK_FAILED, wxCommandEvent);
}} // namespace Slic3r::GUI
#endif // ARRANGEJOB_HPP