mirror of
https://github.com/QIDITECH/QIDIStudio.git
synced 2026-02-02 09:58:41 +03:00
update
This commit is contained in:
798
src/slic3r/GUI/Jobs/ArrangeJob.cpp
Normal file
798
src/slic3r/GUI/Jobs/ArrangeJob.cpp
Normal 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
|
||||
84
src/slic3r/GUI/Jobs/ArrangeJob.hpp
Normal file
84
src/slic3r/GUI/Jobs/ArrangeJob.hpp
Normal 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
|
||||
165
src/slic3r/GUI/Jobs/BindJob.cpp
Normal file
165
src/slic3r/GUI/Jobs/BindJob.cpp
Normal 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
|
||||
52
src/slic3r/GUI/Jobs/BindJob.hpp
Normal file
52
src/slic3r/GUI/Jobs/BindJob.hpp
Normal 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
|
||||
324
src/slic3r/GUI/Jobs/FillBedJob.cpp
Normal file
324
src/slic3r/GUI/Jobs/FillBedJob.cpp
Normal 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
|
||||
48
src/slic3r/GUI/Jobs/FillBedJob.hpp
Normal file
48
src/slic3r/GUI/Jobs/FillBedJob.hpp
Normal 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
167
src/slic3r/GUI/Jobs/Job.cpp
Normal 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
132
src/slic3r/GUI/Jobs/Job.hpp
Normal 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
|
||||
43
src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp
Normal file
43
src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp
Normal 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
|
||||
28
src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp
Normal file
28
src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp
Normal 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
|
||||
238
src/slic3r/GUI/Jobs/OrientJob.cpp
Normal file
238
src/slic3r/GUI/Jobs/OrientJob.cpp
Normal 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(¶ms, ¶ms_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
|
||||
66
src/slic3r/GUI/Jobs/OrientJob.hpp
Normal file
66
src/slic3r/GUI/Jobs/OrientJob.hpp
Normal 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
|
||||
17
src/slic3r/GUI/Jobs/PlaterJob.cpp
Normal file
17
src/slic3r/GUI/Jobs/PlaterJob.cpp
Normal 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
|
||||
26
src/slic3r/GUI/Jobs/PlaterJob.hpp
Normal file
26
src/slic3r/GUI/Jobs/PlaterJob.hpp
Normal 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
|
||||
678
src/slic3r/GUI/Jobs/PrintJob.cpp
Normal file
678
src/slic3r/GUI/Jobs/PrintJob.cpp
Normal 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
|
||||
120
src/slic3r/GUI/Jobs/PrintJob.hpp
Normal file
120
src/slic3r/GUI/Jobs/PrintJob.hpp
Normal 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
|
||||
32
src/slic3r/GUI/Jobs/ProgressIndicator.hpp
Normal file
32
src/slic3r/GUI/Jobs/ProgressIndicator.hpp
Normal 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
|
||||
121
src/slic3r/GUI/Jobs/RotoptimizeJob.cpp
Normal file
121
src/slic3r/GUI/Jobs/RotoptimizeJob.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}}
|
||||
76
src/slic3r/GUI/Jobs/RotoptimizeJob.hpp
Normal file
76
src/slic3r/GUI/Jobs/RotoptimizeJob.hpp
Normal 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 ¶ms)>;
|
||||
|
||||
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
|
||||
249
src/slic3r/GUI/Jobs/SLAImportJob.cpp
Normal file
249
src/slic3r/GUI/Jobs/SLAImportJob.cpp
Normal 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
|
||||
27
src/slic3r/GUI/Jobs/SLAImportJob.hpp
Normal file
27
src/slic3r/GUI/Jobs/SLAImportJob.hpp
Normal 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
|
||||
418
src/slic3r/GUI/Jobs/SendJob.cpp
Normal file
418
src/slic3r/GUI/Jobs/SendJob.cpp
Normal 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
|
||||
71
src/slic3r/GUI/Jobs/SendJob.hpp
Normal file
71
src/slic3r/GUI/Jobs/SendJob.hpp
Normal 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
|
||||
144
src/slic3r/GUI/Jobs/UpgradeNetworkJob.cpp
Normal file
144
src/slic3r/GUI/Jobs/UpgradeNetworkJob.cpp
Normal 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
|
||||
62
src/slic3r/GUI/Jobs/UpgradeNetworkJob.hpp
Normal file
62
src/slic3r/GUI/Jobs/UpgradeNetworkJob.hpp
Normal 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
|
||||
Reference in New Issue
Block a user