mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-23 08:41:11 -06:00
Merge branch 'master' of https://github.com/prusa3d/Slic3r into et_canvas_gui_refactoring
This commit is contained in:
commit
6ca49c05df
30 changed files with 747 additions and 242 deletions
|
@ -98,8 +98,9 @@ void BackgroundSlicingProcess::process_sla()
|
|||
m_print->process();
|
||||
if (this->set_step_started(bspsGCodeFinalize)) {
|
||||
if (! m_export_path.empty()) {
|
||||
m_sla_print->export_raster(m_export_path);
|
||||
m_print->set_status(100, "Masked SLA file exported to " + m_export_path);
|
||||
const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
|
||||
m_sla_print->export_raster(export_path);
|
||||
m_print->set_status(100, "Masked SLA file exported to " + export_path);
|
||||
} else if (! m_upload_job.empty()) {
|
||||
prepare_upload();
|
||||
} else {
|
||||
|
@ -389,7 +390,7 @@ void BackgroundSlicingProcess::prepare_upload()
|
|||
|
||||
// Generate a unique temp path to which the gcode/zip file is copied/exported
|
||||
boost::filesystem::path source_path = boost::filesystem::temp_directory_path()
|
||||
/ boost::filesystem::unique_path(".printhost.%%%%-%%%%-%%%%-%%%%.gcode");
|
||||
/ boost::filesystem::unique_path(".Slic3rPE.upload.%%%%-%%%%-%%%%-%%%%");
|
||||
|
||||
if (m_print == m_fff_print) {
|
||||
m_print->set_status(95, "Running post-processing scripts");
|
||||
|
@ -399,8 +400,8 @@ void BackgroundSlicingProcess::prepare_upload()
|
|||
run_post_process_scripts(source_path.string(), m_fff_print->config());
|
||||
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||
} else {
|
||||
m_sla_print->export_raster(source_path.string());
|
||||
// TODO: Also finalize upload path like with FFF when there are statistics for SLA print
|
||||
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||
m_sla_print->export_raster(source_path.string(), m_upload_job.upload_data.upload_path.string());
|
||||
}
|
||||
|
||||
m_print->set_status(100, (boost::format("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue") % m_upload_job.printhost->get_host()).str());
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "BitmapCache.hpp"
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#if ! defined(WIN32) && ! defined(__APPLE__)
|
||||
#define BROKEN_ALPHA
|
||||
#endif
|
||||
|
@ -9,6 +11,11 @@
|
|||
#include <wx/rawbmp.h>
|
||||
#endif /* BROKEN_ALPHA */
|
||||
|
||||
#define NANOSVG_IMPLEMENTATION
|
||||
#include "nanosvg/nanosvg.h"
|
||||
#define NANOSVGRAST_IMPLEMENTATION
|
||||
#include "nanosvg/nanosvgrast.h"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
void BitmapCache::clear()
|
||||
|
@ -155,6 +162,86 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *beg
|
|||
#endif
|
||||
}
|
||||
|
||||
wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned int width, unsigned int height, const unsigned char *raw_data)
|
||||
{
|
||||
wxImage image(width, height);
|
||||
image.InitAlpha();
|
||||
unsigned char *rgb = image.GetData();
|
||||
unsigned char *alpha = image.GetAlpha();
|
||||
unsigned int pixels = width * height;
|
||||
for (unsigned int i = 0; i < pixels; ++ i) {
|
||||
*rgb ++ = *raw_data ++;
|
||||
*rgb ++ = *raw_data ++;
|
||||
*rgb ++ = *raw_data ++;
|
||||
*alpha ++ = *raw_data ++;
|
||||
}
|
||||
return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)));
|
||||
}
|
||||
|
||||
wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned int width, unsigned int height)
|
||||
{
|
||||
std::string bitmap_key = bitmap_name + ( height !=0 ?
|
||||
"-h" + std::to_string(height) :
|
||||
"-w" + std::to_string(width));
|
||||
auto it = m_map.find(bitmap_key);
|
||||
if (it != m_map.end())
|
||||
return it->second;
|
||||
|
||||
wxImage image;
|
||||
if (! image.LoadFile(Slic3r::GUI::from_u8(Slic3r::var(bitmap_name + ".png")), wxBITMAP_TYPE_PNG) ||
|
||||
image.GetWidth() == 0 || image.GetHeight() == 0)
|
||||
return nullptr;
|
||||
|
||||
if (height != 0 && image.GetHeight() != height)
|
||||
width = int(0.5f + float(image.GetWidth()) * height / image.GetHeight());
|
||||
else if (width != 0 && image.GetWidth() != width)
|
||||
height = int(0.5f + float(image.GetHeight()) * width / image.GetWidth());
|
||||
|
||||
if (height != 0 && width != 0)
|
||||
image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR);
|
||||
|
||||
return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)));
|
||||
}
|
||||
|
||||
wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned int target_width, unsigned int target_height)
|
||||
{
|
||||
std::string bitmap_key = bitmap_name + (target_height != 0 ?
|
||||
"-h" + std::to_string(target_height) :
|
||||
"-w" + std::to_string(target_width));
|
||||
auto it = m_map.find(bitmap_key);
|
||||
if (it != m_map.end())
|
||||
return it->second;
|
||||
|
||||
NSVGimage *image = ::nsvgParseFromFile(Slic3r::var(bitmap_name + ".svg").c_str(), "px", 96.0f);
|
||||
if (image == nullptr)
|
||||
return nullptr;
|
||||
|
||||
float scale = target_height != 0 ?
|
||||
(float)target_height / image->height : target_width != 0 ?
|
||||
(float)target_width / image->width : 1;
|
||||
|
||||
int width = (int)(scale * image->width + 0.5f);
|
||||
int height = (int)(scale * image->height + 0.5f);
|
||||
int n_pixels = width * height;
|
||||
if (n_pixels <= 0) {
|
||||
::nsvgDelete(image);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NSVGrasterizer *rast = ::nsvgCreateRasterizer();
|
||||
if (rast == nullptr) {
|
||||
::nsvgDelete(image);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> data(n_pixels * 4, 0);
|
||||
::nsvgRasterize(rast, image, 0, 0, scale, data.data(), width, height, width * 4);
|
||||
::nsvgDeleteRasterizer(rast);
|
||||
::nsvgDelete(image);
|
||||
|
||||
return this->insert_raw_rgba(bitmap_key, width, height, data.data());
|
||||
}
|
||||
|
||||
wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency)
|
||||
{
|
||||
wxImage image(width, height);
|
||||
|
|
|
@ -29,6 +29,12 @@ public:
|
|||
wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3);
|
||||
wxBitmap* insert(const std::string &name, const std::vector<wxBitmap> &bmps) { return this->insert(name, &bmps.front(), &bmps.front() + bmps.size()); }
|
||||
wxBitmap* insert(const std::string &name, const wxBitmap *begin, const wxBitmap *end);
|
||||
wxBitmap* insert_raw_rgba(const std::string &bitmap_key, unsigned int width, unsigned int height, const unsigned char *raw_data);
|
||||
|
||||
// Load png from resources/icons. bitmap_key is given without the .png suffix. Bitmap will be rescaled to provided height/width if nonzero.
|
||||
wxBitmap* load_png(const std::string &bitmap_key, unsigned int width = 0, unsigned int height = 0);
|
||||
// Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height/width.
|
||||
wxBitmap* load_svg(const std::string &bitmap_key, unsigned int width = 0, unsigned int height = 0);
|
||||
|
||||
static wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency);
|
||||
static wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); }
|
||||
|
|
|
@ -3283,7 +3283,7 @@ bool GLCanvas3D::_init_toolbar()
|
|||
item.sprite_id = 8;
|
||||
item.is_toggable = true;
|
||||
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); };
|
||||
item.visibility_callback = GLToolbarItem::Default_Visibility_Callback;
|
||||
item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; };
|
||||
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); };
|
||||
if (!m_toolbar.add_item(item))
|
||||
return false;
|
||||
|
|
|
@ -13,15 +13,11 @@
|
|||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#define NANOSVG_IMPLEMENTATION
|
||||
#include "nanosvg/nanosvg.h"
|
||||
#define NANOSVGRAST_IMPLEMENTATION
|
||||
#include "nanosvg/nanosvgrast.h"
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
@ -380,6 +376,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns
|
|||
if (n_pixels <= 0)
|
||||
{
|
||||
reset();
|
||||
nsvgDelete(image);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -82,13 +82,31 @@ ObjectList::ObjectList(wxWindow* parent) :
|
|||
init_icons();
|
||||
|
||||
// describe control behavior
|
||||
Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxEvent& event) {
|
||||
Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent& event) {
|
||||
#ifndef __APPLE__
|
||||
// On Windows and Linux, forces a kill focus emulation on the object manipulator fields because this event handler is called
|
||||
// before the kill focus event handler on the object manipulator when changing selection in the list, invalidating the object
|
||||
// manipulator cache with the following call to selection_changed()
|
||||
wxGetApp().obj_manipul()->emulate_kill_focus();
|
||||
#endif // __APPLE__
|
||||
|
||||
/* For multiple selection with pressed SHIFT,
|
||||
* event.GetItem() returns value of a first item in selection list
|
||||
* instead of real last clicked item.
|
||||
* So, let check last selected item in such strange way
|
||||
*/
|
||||
if (wxGetKeyState(WXK_SHIFT))
|
||||
{
|
||||
wxDataViewItemArray sels;
|
||||
GetSelections(sels);
|
||||
if (sels.front() == m_last_selected_item)
|
||||
m_last_selected_item = sels.back();
|
||||
else
|
||||
m_last_selected_item = event.GetItem();
|
||||
}
|
||||
else
|
||||
m_last_selected_item = event.GetItem();
|
||||
|
||||
selection_changed();
|
||||
#ifndef __WXMSW__
|
||||
set_tooltip_for_item(get_mouse_position_in_control());
|
||||
|
@ -509,7 +527,7 @@ void ObjectList::key_event(wxKeyEvent& event)
|
|||
) {
|
||||
remove();
|
||||
}
|
||||
else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_SHIFT))
|
||||
else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL/*WXK_SHIFT*/))
|
||||
select_item_all_children();
|
||||
else
|
||||
event.Skip();
|
||||
|
@ -1005,8 +1023,7 @@ wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu)
|
|||
|
||||
void ObjectList::append_menu_items_osx(wxMenu* menu)
|
||||
{
|
||||
append_menu_item(menu, wxID_ANY, _(L("Delete item")), "",
|
||||
[this](wxCommandEvent&) { remove(); }, "", menu);
|
||||
append_menu_item_delete(menu);
|
||||
|
||||
append_menu_item(menu, wxID_ANY, _(L("Rename")), "",
|
||||
[this](wxCommandEvent&) { rename_item(); }, "", menu);
|
||||
|
@ -1025,7 +1042,7 @@ void ObjectList::append_menu_item_fix_through_netfabb(wxMenu* menu)
|
|||
|
||||
void ObjectList::append_menu_item_export_stl(wxMenu* menu) const
|
||||
{
|
||||
append_menu_item(menu, wxID_ANY, _(L("Export object as STL")) + dots, "",
|
||||
append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, "",
|
||||
[](wxCommandEvent&) { wxGetApp().plater()->export_stl(true); }, "", menu);
|
||||
menu->AppendSeparator();
|
||||
}
|
||||
|
@ -1061,6 +1078,12 @@ void ObjectList::append_menu_item_change_extruder(wxMenu* menu) const
|
|||
}
|
||||
}
|
||||
|
||||
void ObjectList::append_menu_item_delete(wxMenu* menu)
|
||||
{
|
||||
append_menu_item(menu, wxID_ANY, _(L("Delete")), "",
|
||||
[this](wxCommandEvent&) { remove(); }, "", menu);
|
||||
}
|
||||
|
||||
void ObjectList::create_object_popupmenu(wxMenu *menu)
|
||||
{
|
||||
#ifdef __WXOSX__
|
||||
|
@ -1101,6 +1124,7 @@ void ObjectList::create_part_popupmenu(wxMenu *menu)
|
|||
#endif // __WXOSX__
|
||||
|
||||
append_menu_item_fix_through_netfabb(menu);
|
||||
append_menu_item_export_stl(menu);
|
||||
|
||||
m_menu_item_split_part = append_menu_item_split(menu);
|
||||
|
||||
|
@ -1117,10 +1141,21 @@ void ObjectList::create_part_popupmenu(wxMenu *menu)
|
|||
|
||||
void ObjectList::create_instance_popupmenu(wxMenu*menu)
|
||||
{
|
||||
#ifdef __WXOSX__
|
||||
append_menu_item_delete(menu);
|
||||
#endif // __WXOSX__
|
||||
m_menu_item_split_instances = append_menu_item_instance_to_object(menu);
|
||||
|
||||
/* New behavior logic:
|
||||
* 1. Split Object to several separated object, if ALL instances are selected
|
||||
* 2. Separate selected instances from the initial object to the separated object,
|
||||
* if some (not all) instances are selected
|
||||
*/
|
||||
wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) {
|
||||
evt.Enable(can_split_instances()); }, m_menu_item_split_instances->GetId());
|
||||
// evt.Enable(can_split_instances()); }, m_menu_item_split_instances->GetId());
|
||||
evt.SetText(wxGetApp().plater()->canvas3D()->get_selection().is_single_full_object() ?
|
||||
_(L("Set as a Separated Objects")) : _(L("Set as a Separated Object")));
|
||||
}, m_menu_item_split_instances->GetId());
|
||||
}
|
||||
|
||||
wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu)
|
||||
|
@ -1551,7 +1586,7 @@ void ObjectList::split()
|
|||
auto opt_keys = model_object->volumes[id]->config.keys();
|
||||
if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) {
|
||||
select_item(m_objects_model->AddSettingsChild(vol_item));
|
||||
Collapse(vol_item);
|
||||
/*Collapse*/Expand(vol_item);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1638,16 +1673,20 @@ void ObjectList::part_selection_changed()
|
|||
bool update_and_show_manipulations = false;
|
||||
bool update_and_show_settings = false;
|
||||
|
||||
if (multiple_selection()) {
|
||||
const auto item = GetSelection();
|
||||
|
||||
if ( multiple_selection() || item && m_objects_model->GetItemType(item) == itInstanceRoot )
|
||||
{
|
||||
og_name = _(L("Group manipulation"));
|
||||
update_and_show_manipulations = true;
|
||||
|
||||
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
|
||||
// don't show manipulation panel for case of all Object's parts selection
|
||||
update_and_show_manipulations = !selection.is_single_full_instance();
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto item = GetSelection();
|
||||
if (item)
|
||||
{
|
||||
bool is_part = false;
|
||||
if (m_objects_model->GetParent(item) == wxDataViewItem(0)) {
|
||||
obj_idx = m_objects_model->GetIdByItem(item);
|
||||
og_name = _(L("Object manipulation"));
|
||||
|
@ -1665,7 +1704,6 @@ void ObjectList::part_selection_changed()
|
|||
}
|
||||
else {
|
||||
og_name = _(L("Part Settings to modify"));
|
||||
is_part = true;
|
||||
auto main_parent = m_objects_model->GetParent(parent);
|
||||
obj_idx = m_objects_model->GetIdByItem(main_parent);
|
||||
const auto volume_id = m_objects_model->GetVolumeIdByItem(parent);
|
||||
|
@ -1675,7 +1713,6 @@ void ObjectList::part_selection_changed()
|
|||
}
|
||||
else if (m_objects_model->GetItemType(item) == itVolume) {
|
||||
og_name = _(L("Part manipulation"));
|
||||
is_part = true;
|
||||
const auto volume_id = m_objects_model->GetVolumeIdByItem(item);
|
||||
m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config;
|
||||
update_and_show_manipulations = true;
|
||||
|
@ -1743,7 +1780,7 @@ void ObjectList::add_object_to_list(size_t obj_idx)
|
|||
auto opt_keys = model_object->volumes[id]->config.keys();
|
||||
if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) {
|
||||
select_item(m_objects_model->AddSettingsChild(vol_item));
|
||||
Collapse(vol_item);
|
||||
/*Collapse*/Expand(vol_item);
|
||||
}
|
||||
}
|
||||
Expand(item);
|
||||
|
@ -1757,7 +1794,7 @@ void ObjectList::add_object_to_list(size_t obj_idx)
|
|||
auto opt_keys = model_object->config.keys();
|
||||
if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) {
|
||||
select_item(m_objects_model->AddSettingsChild(item));
|
||||
Collapse(item);
|
||||
/*Collapse*/Expand(item);
|
||||
}
|
||||
|
||||
#ifndef __WXOSX__
|
||||
|
@ -1923,11 +1960,22 @@ bool ObjectList::multiple_selection() const
|
|||
return GetSelectedItemsCount() > 1;
|
||||
}
|
||||
|
||||
bool ObjectList::is_selected(const ItemType type) const
|
||||
{
|
||||
const wxDataViewItem& item = GetSelection();
|
||||
if (item)
|
||||
return m_objects_model->GetItemType(item) == type;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ObjectList::update_selections()
|
||||
{
|
||||
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
|
||||
wxDataViewItemArray sels;
|
||||
|
||||
m_selection_mode = smInstance;
|
||||
|
||||
// We doesn't update selection if SettingsItem for the current object/part is selected
|
||||
if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings )
|
||||
{
|
||||
|
@ -1941,32 +1989,56 @@ void ObjectList::update_selections()
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (selection.is_single_full_object())
|
||||
else if (selection.is_single_full_object() || selection.is_multiple_full_object())
|
||||
{
|
||||
sels.Add(m_objects_model->GetItemById(selection.get_object_idx()));
|
||||
const Selection::ObjectIdxsToInstanceIdxsMap& objects_content = selection.get_content();
|
||||
for (const auto& object : objects_content) {
|
||||
if (object.second.size() == 1) // object with 1 instance
|
||||
sels.Add(m_objects_model->GetItemById(object.first));
|
||||
else if (object.second.size() > 1) // object with several instances
|
||||
{
|
||||
wxDataViewItemArray current_sels;
|
||||
GetSelections(current_sels);
|
||||
const wxDataViewItem frst_inst_item = m_objects_model->GetItemByInstanceId(object.first, 0);
|
||||
|
||||
bool root_is_selected = false;
|
||||
for (const auto& item:current_sels)
|
||||
if (item == m_objects_model->GetParent(frst_inst_item) ||
|
||||
item == m_objects_model->GetTopParent(frst_inst_item)) {
|
||||
root_is_selected = true;
|
||||
sels.Add(item);
|
||||
break;
|
||||
}
|
||||
if (root_is_selected)
|
||||
continue;
|
||||
|
||||
const Selection::InstanceIdxsList& instances = object.second;
|
||||
for (const auto& inst : instances)
|
||||
sels.Add(m_objects_model->GetItemByInstanceId(object.first, inst));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (selection.is_single_volume() || selection.is_modifier() ||
|
||||
selection.is_multiple_volume() || selection.is_multiple_full_object()) {
|
||||
else if (selection.is_single_volume() || selection.is_modifier() || selection.is_multiple_volume())
|
||||
{
|
||||
for (auto idx : selection.get_volume_idxs()) {
|
||||
const auto gl_vol = selection.get_volume(idx);
|
||||
if (selection.is_multiple_full_object())
|
||||
sels.Add(m_objects_model->GetItemById(gl_vol->object_idx()));
|
||||
else if (gl_vol->volume_idx() >= 0)
|
||||
if (gl_vol->volume_idx() >= 0)
|
||||
// Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids
|
||||
// are not associated with ModelVolumes, but they are temporarily generated by the backend
|
||||
// (for example, SLA supports or SLA pad).
|
||||
sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx()));
|
||||
}
|
||||
m_selection_mode = smVolume;
|
||||
}
|
||||
else if (selection.is_single_full_instance() || selection.is_multiple_full_instance()) {
|
||||
else if (selection.is_single_full_instance() || selection.is_multiple_full_instance())
|
||||
{
|
||||
for (auto idx : selection.get_instance_idxs()) {
|
||||
sels.Add(m_objects_model->GetItemByInstanceId(selection.get_object_idx(), idx));
|
||||
}
|
||||
}
|
||||
else if (selection.is_mixed())
|
||||
{
|
||||
auto& objects_content_list = selection.get_content();
|
||||
const Selection::ObjectIdxsToInstanceIdxsMap& objects_content_list = selection.get_content();
|
||||
|
||||
for (auto idx : selection.get_volume_idxs()) {
|
||||
const auto gl_vol = selection.get_volume(idx);
|
||||
|
@ -1999,6 +2071,9 @@ void ObjectList::update_selections()
|
|||
sels.Add(m_objects_model->GetItemByVolumeId(glv_obj_idx, glv_vol_idx));
|
||||
}
|
||||
}
|
||||
|
||||
if (sels.size() == 0)
|
||||
m_selection_mode = smUndef;
|
||||
|
||||
select_items(sels);
|
||||
|
||||
|
@ -2034,29 +2109,34 @@ void ObjectList::update_selections_on_canvas()
|
|||
|
||||
auto add_to_selection = [this](const wxDataViewItem& item, Selection& selection, int instance_idx, bool as_single_selection)
|
||||
{
|
||||
if (m_objects_model->GetParent(item) == wxDataViewItem(0)) {
|
||||
selection.add_object(m_objects_model->GetIdByItem(item), as_single_selection);
|
||||
const ItemType& type = m_objects_model->GetItemType(item);
|
||||
if ( type == itInstanceRoot || m_objects_model->GetParent(item) == wxDataViewItem(0) ) {
|
||||
wxDataViewItem obj_item = type == itInstanceRoot ? m_objects_model->GetParent(item) : item;
|
||||
selection.add_object(m_objects_model->GetIdByItem(obj_item), as_single_selection);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_objects_model->GetItemType(item) == itVolume) {
|
||||
if (type == itVolume) {
|
||||
const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item));
|
||||
const int vol_idx = m_objects_model->GetVolumeIdByItem(item);
|
||||
selection.add_volume(obj_idx, vol_idx, std::max(instance_idx, 0), as_single_selection);
|
||||
}
|
||||
else if (m_objects_model->GetItemType(item) == itInstance) {
|
||||
else if (type == itInstance) {
|
||||
const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item));
|
||||
const int inst_idx = m_objects_model->GetInstanceIdByItem(item);
|
||||
selection.add_instance(obj_idx, inst_idx, as_single_selection);
|
||||
}
|
||||
};
|
||||
|
||||
// stores current instance idx before to clear the selection
|
||||
int instance_idx = selection.get_instance_idx();
|
||||
|
||||
if (sel_cnt == 1) {
|
||||
wxDataViewItem item = GetSelection();
|
||||
if (m_objects_model->GetItemType(item) & (itSettings|itInstanceRoot))
|
||||
add_to_selection(m_objects_model->GetParent(item), selection, -1, true);
|
||||
add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, true);
|
||||
else
|
||||
add_to_selection(item, selection, -1, true);
|
||||
add_to_selection(item, selection, instance_idx, true);
|
||||
|
||||
wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state();
|
||||
return;
|
||||
|
@ -2065,8 +2145,6 @@ void ObjectList::update_selections_on_canvas()
|
|||
wxDataViewItemArray sels;
|
||||
GetSelections(sels);
|
||||
|
||||
// stores current instance idx before to clear the selection
|
||||
int instance_idx = selection.get_instance_idx();
|
||||
selection.clear();
|
||||
for (auto item: sels)
|
||||
add_to_selection(item, selection, instance_idx, false);
|
||||
|
@ -2112,22 +2190,101 @@ void ObjectList::select_item_all_children()
|
|||
if (!GetSelection() || m_objects_model->GetItemType(GetSelection()) == itObject) {
|
||||
for (int i = 0; i < m_objects->size(); i++)
|
||||
sels.Add(m_objects_model->GetItemById(i));
|
||||
m_selection_mode = smInstance;
|
||||
}
|
||||
else {
|
||||
const auto item = GetSelection();
|
||||
// Some volume(instance) is selected => select all volumes(instances) inside the current object
|
||||
if (m_objects_model->GetItemType(item) & (itVolume | itInstance)) {
|
||||
if (m_objects_model->GetItemType(item) & (itVolume | itInstance))
|
||||
m_objects_model->GetChildren(m_objects_model->GetParent(item), sels);
|
||||
}
|
||||
|
||||
m_selection_mode = m_objects_model->GetItemType(item)&itVolume ? smVolume : smInstance;
|
||||
}
|
||||
|
||||
SetSelections(sels);
|
||||
selection_changed();
|
||||
}
|
||||
|
||||
// update selection mode for non-multiple selection
|
||||
void ObjectList::update_selection_mode()
|
||||
{
|
||||
// All items are unselected
|
||||
if (!GetSelection())
|
||||
{
|
||||
m_last_selected_item = wxDataViewItem(0);
|
||||
m_selection_mode = smUndef;
|
||||
return;
|
||||
}
|
||||
|
||||
const ItemType type = m_objects_model->GetItemType(GetSelection());
|
||||
m_selection_mode = type&itSettings ? smUndef :
|
||||
type&itVolume ? smVolume : smInstance;
|
||||
}
|
||||
|
||||
// check last selected item. If is it possible to select it
|
||||
bool ObjectList::check_last_selection(wxString& msg_str)
|
||||
{
|
||||
if (!m_last_selected_item)
|
||||
return true;
|
||||
|
||||
const bool is_shift_pressed = wxGetKeyState(WXK_SHIFT);
|
||||
|
||||
/* We can't mix Parts and Objects/Instances.
|
||||
* So, show information about it
|
||||
*/
|
||||
const ItemType type = m_objects_model->GetItemType(m_last_selected_item);
|
||||
|
||||
// check a case of a selection of the Parts from different Objects
|
||||
bool impossible_multipart_selection = false;
|
||||
if (type & itVolume && m_selection_mode == smVolume)
|
||||
{
|
||||
wxDataViewItemArray sels;
|
||||
GetSelections(sels);
|
||||
for (const auto& sel: sels)
|
||||
if (sel != m_last_selected_item &&
|
||||
m_objects_model->GetParent(sel) != m_objects_model->GetParent(m_last_selected_item))
|
||||
{
|
||||
impossible_multipart_selection = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (impossible_multipart_selection ||
|
||||
type & itSettings ||
|
||||
type & itVolume && m_selection_mode == smInstance ||
|
||||
!(type & itVolume) && m_selection_mode == smVolume)
|
||||
{
|
||||
// Inform user why selection isn't complited
|
||||
const wxString item_type = m_selection_mode == smInstance ? _(L("Object or Instance")) : _(L("Part"));
|
||||
|
||||
msg_str = wxString::Format( _(L("Unsupported selection")) + "\n\n" +
|
||||
_(L("You started your selection with %s Item.")) + "\n" +
|
||||
_(L("In this mode you can select only other %s Items%s")),
|
||||
item_type, item_type,
|
||||
m_selection_mode == smInstance ? "." :
|
||||
" " + _(L("of a current Object")));
|
||||
|
||||
// Unselect last selected item, if selection is without SHIFT
|
||||
if (!is_shift_pressed) {
|
||||
Unselect(m_last_selected_item);
|
||||
show_info(this, msg_str, _(L("Info")));
|
||||
}
|
||||
|
||||
return is_shift_pressed;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ObjectList::fix_multiselection_conflicts()
|
||||
{
|
||||
if (GetSelectedItemsCount() <= 1)
|
||||
if (GetSelectedItemsCount() <= 1) {
|
||||
update_selection_mode();
|
||||
return;
|
||||
}
|
||||
|
||||
wxString msg_string;
|
||||
if (!check_last_selection(msg_string))
|
||||
return;
|
||||
|
||||
m_prevent_list_events = true;
|
||||
|
@ -2135,12 +2292,58 @@ void ObjectList::fix_multiselection_conflicts()
|
|||
wxDataViewItemArray sels;
|
||||
GetSelections(sels);
|
||||
|
||||
for (auto item : sels) {
|
||||
if (m_objects_model->GetItemType(item) & (itSettings|itInstanceRoot))
|
||||
Unselect(item);
|
||||
else if (m_objects_model->GetParent(item) != wxDataViewItem(0))
|
||||
Unselect(m_objects_model->GetParent(item));
|
||||
if (m_selection_mode == smVolume)
|
||||
{
|
||||
// identify correct parent of the initial selected item
|
||||
const wxDataViewItem& parent = m_objects_model->GetParent(m_last_selected_item == sels.front() ? sels.back() : sels.front());
|
||||
|
||||
sels.clear();
|
||||
wxDataViewItemArray children; // selected volumes from current parent
|
||||
m_objects_model->GetChildren(parent, children);
|
||||
|
||||
for (const auto child : children)
|
||||
if (IsSelected(child) && m_objects_model->GetItemType(child)&itVolume)
|
||||
sels.Add(child);
|
||||
|
||||
// If some part is selected, unselect all items except of selected parts of the current object
|
||||
UnselectAll();
|
||||
SetSelections(sels);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto item : sels)
|
||||
{
|
||||
if (!IsSelected(item)) // if this item is unselected now (from previous actions)
|
||||
continue;
|
||||
|
||||
if (m_objects_model->GetItemType(item) & itSettings) {
|
||||
Unselect(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
const wxDataViewItem& parent = m_objects_model->GetParent(item);
|
||||
if (parent != wxDataViewItem(0) && IsSelected(parent))
|
||||
Unselect(parent);
|
||||
else
|
||||
{
|
||||
wxDataViewItemArray unsels;
|
||||
m_objects_model->GetAllChildren(item, unsels);
|
||||
for (const auto unsel_item : unsels)
|
||||
Unselect(unsel_item);
|
||||
}
|
||||
|
||||
if (m_objects_model->GetItemType(item) & itVolume)
|
||||
Unselect(item);
|
||||
|
||||
m_selection_mode = smInstance;
|
||||
}
|
||||
}
|
||||
|
||||
if (!msg_string.IsEmpty())
|
||||
show_info(this, msg_string, _(L("Info")));
|
||||
|
||||
if (!IsSelected(m_last_selected_item))
|
||||
m_last_selected_item = wxDataViewItem(0);
|
||||
|
||||
m_prevent_list_events = false;
|
||||
}
|
||||
|
@ -2272,6 +2475,12 @@ void ObjectList::update_object_menu()
|
|||
|
||||
void ObjectList::instances_to_separated_object(const int obj_idx, const std::set<int>& inst_idxs)
|
||||
{
|
||||
if ((*m_objects)[obj_idx]->instances.size() == inst_idxs.size())
|
||||
{
|
||||
instances_to_separated_objects(obj_idx);
|
||||
return;
|
||||
}
|
||||
|
||||
// create new object from selected instance
|
||||
ModelObject* model_object = (*m_objects)[obj_idx]->get_model()->add_object(*(*m_objects)[obj_idx]);
|
||||
for (int inst_idx = model_object->instances.size() - 1; inst_idx >= 0; inst_idx--)
|
||||
|
@ -2292,6 +2501,31 @@ void ObjectList::instances_to_separated_object(const int obj_idx, const std::set
|
|||
}
|
||||
}
|
||||
|
||||
void ObjectList::instances_to_separated_objects(const int obj_idx)
|
||||
{
|
||||
const int inst_cnt = (*m_objects)[obj_idx]->instances.size();
|
||||
|
||||
for (int i = inst_cnt-1; i > 0 ; i--)
|
||||
{
|
||||
// create new object from initial
|
||||
ModelObject* object = (*m_objects)[obj_idx]->get_model()->add_object(*(*m_objects)[obj_idx]);
|
||||
for (int inst_idx = object->instances.size() - 1; inst_idx >= 0; inst_idx--)
|
||||
{
|
||||
if (inst_idx == i)
|
||||
continue;
|
||||
// delete unnecessary instances
|
||||
object->delete_instance(inst_idx);
|
||||
}
|
||||
|
||||
// Add new object to the object_list
|
||||
add_object_to_list(m_objects->size() - 1);
|
||||
|
||||
// delete current instance from the initial object
|
||||
del_subobject_from_object(obj_idx, i, itInstance);
|
||||
delete_instance_from_list(obj_idx, i);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectList::split_instances()
|
||||
{
|
||||
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
|
||||
|
@ -2299,6 +2533,12 @@ void ObjectList::split_instances()
|
|||
if (obj_idx == -1)
|
||||
return;
|
||||
|
||||
if (selection.is_single_full_object())
|
||||
{
|
||||
instances_to_separated_objects(obj_idx);
|
||||
return;
|
||||
}
|
||||
|
||||
const int inst_idx = selection.get_instance_idx();
|
||||
const std::set<int> inst_idxs = inst_idx < 0 ?
|
||||
selection.get_instance_idxs() :
|
||||
|
@ -2424,8 +2664,7 @@ void ObjectList::show_multi_selection_menu()
|
|||
wxMenu* menu = new wxMenu();
|
||||
|
||||
#ifdef __WXOSX__
|
||||
append_menu_item(menu, wxID_ANY, _(L("Delete items")), "",
|
||||
[this](wxCommandEvent&) { remove(); }, "", menu);
|
||||
append_menu_item_delete(menu);
|
||||
#endif //__WXOSX__
|
||||
|
||||
if (extruders_count() > 1)
|
||||
|
|
|
@ -60,6 +60,12 @@ struct ItemForDelete
|
|||
|
||||
class ObjectList : public wxDataViewCtrl
|
||||
{
|
||||
enum SELECTION_MODE
|
||||
{
|
||||
smUndef,
|
||||
smVolume,
|
||||
smInstance
|
||||
} m_selection_mode {smUndef};
|
||||
|
||||
struct dragged_item_data
|
||||
{
|
||||
|
@ -135,6 +141,7 @@ class ObjectList : public wxDataViewCtrl
|
|||
bool m_part_settings_changed = false;
|
||||
|
||||
int m_selected_row = 0;
|
||||
wxDataViewItem m_last_selected_item {nullptr};
|
||||
|
||||
#if 0
|
||||
FreqSettingsBundle m_freq_settings_fff;
|
||||
|
@ -188,6 +195,7 @@ public:
|
|||
void append_menu_item_fix_through_netfabb(wxMenu* menu);
|
||||
void append_menu_item_export_stl(wxMenu* menu) const ;
|
||||
void append_menu_item_change_extruder(wxMenu* menu) const;
|
||||
void append_menu_item_delete(wxMenu* menu);
|
||||
void create_object_popupmenu(wxMenu *menu);
|
||||
void create_sla_object_popupmenu(wxMenu*menu);
|
||||
void create_part_popupmenu(wxMenu*menu);
|
||||
|
@ -251,12 +259,15 @@ public:
|
|||
|
||||
void init_objects();
|
||||
bool multiple_selection() const ;
|
||||
bool is_selected(const ItemType type) const;
|
||||
void update_selections();
|
||||
void update_selections_on_canvas();
|
||||
void select_item(const wxDataViewItem& item);
|
||||
void select_items(const wxDataViewItemArray& sels);
|
||||
void select_all();
|
||||
void select_item_all_children();
|
||||
void update_selection_mode();
|
||||
bool check_last_selection(wxString& msg_str);
|
||||
// correct current selections to avoid of the possible conflicts
|
||||
void fix_multiselection_conflicts();
|
||||
|
||||
|
@ -269,6 +280,7 @@ public:
|
|||
void update_object_menu();
|
||||
|
||||
void instances_to_separated_object(const int obj_idx, const std::set<int>& inst_idx);
|
||||
void instances_to_separated_objects(const int obj_idx);
|
||||
void split_instances();
|
||||
void rename_item();
|
||||
void fix_through_netfabb() const;
|
||||
|
|
|
@ -161,6 +161,8 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
|
|||
m_new_move_label_string = L("Position");
|
||||
m_new_rotate_label_string = L("Rotation");
|
||||
m_new_scale_label_string = L("Scale factors");
|
||||
|
||||
ObjectList* obj_list = wxGetApp().obj_list();
|
||||
if (selection.is_single_full_instance())
|
||||
{
|
||||
// all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one
|
||||
|
@ -187,7 +189,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
|
|||
|
||||
m_new_enabled = true;
|
||||
}
|
||||
else if (selection.is_single_full_object())
|
||||
else if (selection.is_single_full_object() && obj_list->is_selected(itObject))
|
||||
{
|
||||
m_cache.instance.reset();
|
||||
|
||||
|
@ -212,7 +214,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
|
|||
m_new_size = (volume->get_volume_transformation().get_matrix(true, true) * volume->bounding_box.size()).cwiseAbs();
|
||||
m_new_enabled = true;
|
||||
}
|
||||
else if (wxGetApp().obj_list()->multiple_selection())
|
||||
else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot))
|
||||
{
|
||||
reset_settings_value();
|
||||
m_new_move_label_string = L("Translate");
|
||||
|
|
|
@ -16,12 +16,6 @@ namespace Slic3r {
|
|||
namespace GUI {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// GLGizmoCut
|
||||
|
||||
class GLGizmoCutPanel : public wxPanel
|
||||
{
|
||||
public:
|
||||
|
@ -193,7 +187,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, co
|
|||
m_imgui->set_next_window_bg_alpha(0.5f);
|
||||
m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
ImGui::PushItemWidth(100.0f);
|
||||
ImGui::PushItemWidth(m_imgui->scaled(5.0f));
|
||||
bool _value_changed = ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f");
|
||||
|
||||
m_imgui->checkbox(_(L("Keep upper part")), m_keep_upper);
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
#include "libslic3r/SLA/SLARotfinder.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#include "libnest2d/optimizers/nlopt/genetic.hpp"
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
|
@ -1199,7 +1201,7 @@ struct Plater::priv
|
|||
BoundingBox scaled_bed_shape_bb() const;
|
||||
std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config);
|
||||
std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects);
|
||||
std::unique_ptr<CheckboxFileDialog> get_export_file(GUI::FileType file_type);
|
||||
wxString get_export_file(GUI::FileType file_type);
|
||||
|
||||
const Selection& get_selection() const;
|
||||
Selection& get_selection();
|
||||
|
@ -1617,6 +1619,45 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
}
|
||||
#if ENABLE_VOLUMES_CENTERING_FIXES
|
||||
}
|
||||
else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf))
|
||||
{
|
||||
bool advanced = false;
|
||||
for (const ModelObject* model_object : model.objects)
|
||||
{
|
||||
// is there more than one instance ?
|
||||
if (model_object->instances.size() > 1)
|
||||
{
|
||||
advanced = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// is there any modifier ?
|
||||
for (const ModelVolume* model_volume : model_object->volumes)
|
||||
{
|
||||
if (!model_volume->is_model_part())
|
||||
{
|
||||
advanced = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (advanced)
|
||||
break;
|
||||
}
|
||||
|
||||
if (advanced)
|
||||
{
|
||||
wxMessageDialog dlg(q, _(L("This file cannot be loaded in simple mode. Do you want to switch to expert mode?\n")),
|
||||
_(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO);
|
||||
if (dlg.ShowModal() == wxID_YES)
|
||||
{
|
||||
Slic3r::GUI::wxGetApp().save_mode(comExpert);
|
||||
view3D->set_as_dirty();
|
||||
}
|
||||
else
|
||||
return obj_idxs;
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_VOLUMES_CENTERING_FIXES
|
||||
|
||||
#if !ENABLE_VOLUMES_CENTERING_FIXES
|
||||
|
@ -1642,7 +1683,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
Slic3r::GUI::show_error(nullptr,
|
||||
wxString::Format(_(L("You can't to add the object(s) from %s because of one or some of them is(are) multi-part")),
|
||||
from_path(filename)));
|
||||
return std::vector<size_t>();
|
||||
return obj_idxs;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1784,7 +1825,7 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode
|
|||
return obj_idxs;
|
||||
}
|
||||
|
||||
std::unique_ptr<CheckboxFileDialog> Plater::priv::get_export_file(GUI::FileType file_type)
|
||||
wxString Plater::priv::get_export_file(GUI::FileType file_type)
|
||||
{
|
||||
wxString wildcard;
|
||||
switch (file_type) {
|
||||
|
@ -1801,34 +1842,56 @@ std::unique_ptr<CheckboxFileDialog> Plater::priv::get_export_file(GUI::FileType
|
|||
|
||||
// Update printbility state of each of the ModelInstances.
|
||||
this->update_print_volume_state();
|
||||
// Find the file name of the first printable object.
|
||||
fs::path output_file = this->model.propose_export_file_name_and_path();
|
||||
|
||||
const Selection& selection = get_selection();
|
||||
int obj_idx = selection.get_object_idx();
|
||||
|
||||
fs::path output_file;
|
||||
// first try to get the file name from the current selection
|
||||
if ((0 <= obj_idx) && (obj_idx < (int)this->model.objects.size()))
|
||||
output_file = this->model.objects[obj_idx]->get_export_filename();
|
||||
|
||||
if (output_file.empty())
|
||||
// Find the file name of the first printable object.
|
||||
output_file = this->model.propose_export_file_name_and_path();
|
||||
|
||||
wxString dlg_title;
|
||||
switch (file_type) {
|
||||
case FT_STL: output_file.replace_extension("stl"); break;
|
||||
case FT_AMF: output_file.replace_extension("zip.amf"); break; // XXX: Problem on OS X with double extension?
|
||||
case FT_3MF: output_file.replace_extension("3mf"); break;
|
||||
case FT_STL:
|
||||
{
|
||||
output_file.replace_extension("stl");
|
||||
dlg_title = _(L("Export STL file:"));
|
||||
break;
|
||||
}
|
||||
case FT_AMF:
|
||||
{
|
||||
// XXX: Problem on OS X with double extension?
|
||||
output_file.replace_extension("zip.amf");
|
||||
dlg_title = _(L("Export AMF file:"));
|
||||
break;
|
||||
}
|
||||
case FT_3MF:
|
||||
{
|
||||
output_file.replace_extension("3mf");
|
||||
dlg_title = _(L("Save file as:"));
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
|
||||
auto dlg = Slic3r::make_unique<CheckboxFileDialog>(q,
|
||||
((file_type == FT_AMF) || (file_type == FT_3MF)) ? _(L("Export print config")) : "",
|
||||
true,
|
||||
_(L("Save file as:")),
|
||||
from_path(output_file.parent_path()),
|
||||
from_path(output_file.filename()),
|
||||
wildcard,
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT
|
||||
);
|
||||
wxFileDialog* dlg = new wxFileDialog(q, dlg_title,
|
||||
from_path(output_file.parent_path()), from_path(output_file.filename()),
|
||||
wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
|
||||
if (dlg->ShowModal() != wxID_OK) {
|
||||
return nullptr;
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
fs::path path(into_path(dlg->GetPath()));
|
||||
wxString out_path = dlg->GetPath();
|
||||
fs::path path(into_path(out_path));
|
||||
wxGetApp().app_config->update_last_output_dir(path.parent_path().string());
|
||||
|
||||
return dlg;
|
||||
return out_path;
|
||||
}
|
||||
|
||||
const Selection& Plater::priv::get_selection() const
|
||||
|
@ -2039,7 +2102,9 @@ void Plater::priv::sla_optimize_rotation() {
|
|||
rotoptimizing.store(true);
|
||||
|
||||
int obj_idx = get_selected_object_idx();
|
||||
ModelObject * o = model.objects[obj_idx];
|
||||
if(obj_idx < 0) { rotoptimizing.store(false); return; }
|
||||
|
||||
ModelObject * o = model.objects[size_t(obj_idx)];
|
||||
|
||||
background_process.stop();
|
||||
|
||||
|
@ -2047,7 +2112,7 @@ void Plater::priv::sla_optimize_rotation() {
|
|||
statusbar()->set_range(100);
|
||||
|
||||
auto stfn = [this] (unsigned st, const std::string& msg) {
|
||||
statusbar()->set_progress(st);
|
||||
statusbar()->set_progress(int(st));
|
||||
statusbar()->set_status_text(msg);
|
||||
|
||||
// could be problematic, but we need the cancel button.
|
||||
|
@ -2065,8 +2130,59 @@ void Plater::priv::sla_optimize_rotation() {
|
|||
[this](){ return !rotoptimizing.load(); }
|
||||
);
|
||||
|
||||
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
|
||||
assert(bed_shape_opt);
|
||||
|
||||
auto& bedpoints = bed_shape_opt->values;
|
||||
Polyline bed; bed.points.reserve(bedpoints.size());
|
||||
for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
||||
|
||||
double mindist = 6.0; // FIXME
|
||||
double offs = mindist / 2.0 - EPSILON;
|
||||
|
||||
if(rotoptimizing.load()) // wasn't canceled
|
||||
for(ModelInstance * oi : o->instances) oi->set_rotation({r[X], r[Y], r[Z]});
|
||||
for(ModelInstance * oi : o->instances) {
|
||||
oi->set_rotation({r[X], r[Y], r[Z]});
|
||||
|
||||
auto trchull = o->convex_hull_2d(oi->get_transformation().get_matrix());
|
||||
|
||||
namespace opt = libnest2d::opt;
|
||||
opt::StopCriteria stopcr;
|
||||
stopcr.relative_score_difference = 0.01;
|
||||
stopcr.max_iterations = 10000;
|
||||
stopcr.stop_score = 0.0;
|
||||
opt::GeneticOptimizer solver(stopcr);
|
||||
Polygon pbed(bed);
|
||||
|
||||
auto bin = pbed.bounding_box();
|
||||
double binw = bin.size()(X) * SCALING_FACTOR - offs;
|
||||
double binh = bin.size()(Y) * SCALING_FACTOR - offs;
|
||||
|
||||
auto result = solver.optimize_min([&trchull, binw, binh](double rot){
|
||||
auto chull = trchull;
|
||||
chull.rotate(rot);
|
||||
|
||||
auto bb = chull.bounding_box();
|
||||
double bbw = bb.size()(X) * SCALING_FACTOR;
|
||||
double bbh = bb.size()(Y) * SCALING_FACTOR;
|
||||
|
||||
auto wdiff = bbw - binw;
|
||||
auto hdiff = bbh - binh;
|
||||
double diff = 0;
|
||||
if(wdiff < 0 && hdiff < 0) diff = wdiff + hdiff;
|
||||
if(wdiff > 0) diff += wdiff;
|
||||
if(hdiff > 0) diff += hdiff;
|
||||
|
||||
return diff;
|
||||
}, opt::initvals(0.0), opt::bound(-PI/2, PI/2));
|
||||
|
||||
double r = std::get<0>(result.optimum);
|
||||
|
||||
Vec3d rt = oi->get_rotation(); rt(Z) += r;
|
||||
oi->set_rotation(rt);
|
||||
}
|
||||
|
||||
arr::find_new_position(model, o->instances, coord_t(mindist/SCALING_FACTOR), bed);
|
||||
|
||||
// Correct the z offset of the object which was corrupted be the rotation
|
||||
o->ensure_on_bed();
|
||||
|
@ -2714,7 +2830,10 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/
|
|||
if (is_part) {
|
||||
item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")),
|
||||
[this](wxCommandEvent&) { q->remove_selected(); }, "brick_delete.png");
|
||||
} else {
|
||||
|
||||
sidebar->obj_list()->append_menu_item_export_stl(menu);
|
||||
}
|
||||
else {
|
||||
wxMenuItem* item_increase = append_menu_item(menu, wxID_ANY, _(L("Increase copies")) + "\t+", _(L("Place one more copy of the selected object")),
|
||||
[this](wxCommandEvent&) { q->increase_instances(); }, "add.png");
|
||||
wxMenuItem* item_decrease = append_menu_item(menu, wxID_ANY, _(L("Decrease copies")) + "\t-", _(L("Remove one copy of the selected object")),
|
||||
|
@ -2745,10 +2864,11 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/
|
|||
append_menu_item(menu, wxID_ANY, _(L("Reload from Disk")), _(L("Reload the selected file from Disk")),
|
||||
[this](wxCommandEvent&) { reload_from_disk(); });
|
||||
|
||||
append_menu_item(menu, wxID_ANY, _(L("Export object as STL")) + dots, _(L("Export this single object as STL file")),
|
||||
append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, _(L("Export the selected object as STL file")),
|
||||
[this](wxCommandEvent&) { q->export_stl(true); });
|
||||
|
||||
menu->AppendSeparator();
|
||||
}
|
||||
menu->AppendSeparator();
|
||||
|
||||
sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu);
|
||||
|
||||
|
@ -2907,6 +3027,9 @@ bool Plater::priv::can_split() const
|
|||
|
||||
bool Plater::priv::layers_height_allowed() const
|
||||
{
|
||||
if (printer_technology != ptFFF)
|
||||
return false;
|
||||
|
||||
int obj_idx = get_selected_object_idx();
|
||||
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed();
|
||||
}
|
||||
|
@ -3243,11 +3366,8 @@ void Plater::export_stl(bool selection_only)
|
|||
{
|
||||
if (p->model.objects.empty()) { return; }
|
||||
|
||||
auto dialog = p->get_export_file(FT_STL);
|
||||
if (! dialog) { return; }
|
||||
|
||||
// Store a binary STL
|
||||
const wxString path = dialog->GetPath();
|
||||
wxString path = p->get_export_file(FT_STL);
|
||||
if (path.empty()) { return; }
|
||||
const std::string path_u8 = into_u8(path);
|
||||
|
||||
wxBusyCursor wait;
|
||||
|
@ -3259,10 +3379,25 @@ void Plater::export_stl(bool selection_only)
|
|||
|
||||
const auto obj_idx = selection.get_object_idx();
|
||||
if (obj_idx == -1) { return; }
|
||||
mesh = p->model.objects[obj_idx]->mesh();
|
||||
} else {
|
||||
mesh = p->model.mesh();
|
||||
|
||||
const ModelObject* model_object = p->model.objects[obj_idx];
|
||||
if (selection.get_mode() == Selection::Instance)
|
||||
{
|
||||
if (selection.is_single_full_object())
|
||||
mesh = model_object->mesh();
|
||||
else
|
||||
mesh = model_object->full_raw_mesh();
|
||||
}
|
||||
else
|
||||
{
|
||||
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
mesh = model_object->volumes[volume->volume_idx()]->mesh;
|
||||
mesh.transform(volume->get_volume_transformation().get_matrix());
|
||||
mesh.translate(-model_object->origin_translation.cast<float>());
|
||||
}
|
||||
}
|
||||
else
|
||||
mesh = p->model.mesh();
|
||||
|
||||
Slic3r::store_stl(path_u8.c_str(), &mesh, true);
|
||||
p->statusbar()->set_status_text(wxString::Format(_(L("STL file exported to %s")), path));
|
||||
|
@ -3272,15 +3407,14 @@ void Plater::export_amf()
|
|||
{
|
||||
if (p->model.objects.empty()) { return; }
|
||||
|
||||
auto dialog = p->get_export_file(FT_AMF);
|
||||
if (! dialog) { return; }
|
||||
|
||||
const wxString path = dialog->GetPath();
|
||||
wxString path = p->get_export_file(FT_AMF);
|
||||
if (path.empty()) { return; }
|
||||
const std::string path_u8 = into_u8(path);
|
||||
|
||||
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
|
||||
wxBusyCursor wait;
|
||||
if (Slic3r::store_amf(path_u8.c_str(), &p->model, dialog->get_checkbox_value() ? &cfg : nullptr)) {
|
||||
bool export_config = true;
|
||||
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
|
||||
if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) {
|
||||
// Success
|
||||
p->statusbar()->set_status_text(wxString::Format(_(L("AMF file exported to %s")), path));
|
||||
} else {
|
||||
|
@ -3297,10 +3431,8 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
|
|||
bool export_config = true;
|
||||
if (output_path.empty())
|
||||
{
|
||||
auto dialog = p->get_export_file(FT_3MF);
|
||||
if (!dialog) { return; }
|
||||
path = dialog->GetPath();
|
||||
export_config = dialog->get_checkbox_value();
|
||||
path = p->get_export_file(FT_3MF);
|
||||
if (path.empty()) { return; }
|
||||
}
|
||||
else
|
||||
path = from_path(output_path);
|
||||
|
|
|
@ -133,10 +133,12 @@ private:
|
|||
const Transform3d& get_instance_full_matrix() const { return m_instance.full_matrix; }
|
||||
};
|
||||
|
||||
public:
|
||||
typedef std::map<unsigned int, VolumeCache> VolumesCache;
|
||||
typedef std::set<int> InstanceIdxsList;
|
||||
typedef std::map<int, InstanceIdxsList> ObjectIdxsToInstanceIdxsMap;
|
||||
|
||||
private:
|
||||
struct Cache
|
||||
{
|
||||
// Cache of GLVolume derived transformation matrices, valid during mouse dragging.
|
||||
|
|
|
@ -1446,7 +1446,7 @@ void TabFilament::build()
|
|||
line.append_option(optgroup->get_option("bed_temperature"));
|
||||
optgroup->append_line(line);
|
||||
|
||||
page = add_options_page(_(L("Cooling")), "hourglass.png");
|
||||
page = add_options_page(_(L("Cooling")), "cooling");
|
||||
optgroup = page->new_optgroup(_(L("Enable")));
|
||||
optgroup->append_single_option_line("fan_always_on");
|
||||
optgroup->append_single_option_line("cooling");
|
||||
|
@ -1638,7 +1638,8 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup)
|
|||
}
|
||||
|
||||
auto printhost_browse = [=](wxWindow* parent) {
|
||||
auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse ")) + dots,
|
||||
wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
|
||||
btn->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
btn->SetBitmap(create_scaled_bitmap("zoom.png"));
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include <wx/dcclient.h>
|
||||
#include <wx/numformatter.h>
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
#include "BitmapCache.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
|
@ -421,19 +423,21 @@ void PrusaCollapsiblePaneMSW::Collapse(bool collapse)
|
|||
// PrusaObjectDataViewModelNode
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
wxBitmap create_scaled_bitmap(const std::string& bmp_name)
|
||||
// If an icon has horizontal orientation (width > height) call this function with is_horizontal = true
|
||||
wxBitmap create_scaled_bitmap(const std::string& bmp_name_in, const bool is_horizontal /* = false*/)
|
||||
{
|
||||
const double scale_f = Slic3r::GUI::wxGetApp().em_unit()* 0.1;//GetContentScaleFactor();
|
||||
if (scale_f == 1.0)
|
||||
return wxBitmap(Slic3r::GUI::from_u8(Slic3r::var(bmp_name)), wxBITMAP_TYPE_PNG);
|
||||
// else if (scale_f == 2.0) // use biger icon
|
||||
// return wxBitmap(Slic3r::GUI::from_u8(Slic3r::var(bmp_name_X2)), wxBITMAP_TYPE_PNG);
|
||||
static Slic3r::GUI::BitmapCache cache;
|
||||
|
||||
wxImage img = wxImage(Slic3r::GUI::from_u8(Slic3r::var(bmp_name)), wxBITMAP_TYPE_PNG);
|
||||
const int sz_w = int(img.GetWidth()*scale_f);
|
||||
const int sz_h = int(img.GetHeight()*scale_f);
|
||||
img.Rescale(sz_w, sz_h, wxIMAGE_QUALITY_BILINEAR);
|
||||
return wxBitmap(img);
|
||||
unsigned int height, width = height = 0;
|
||||
unsigned int& scale_base = is_horizontal ? width : height;
|
||||
scale_base = (unsigned int)(Slic3r::GUI::wxGetApp().em_unit() * 1.6f + 0.5f);
|
||||
|
||||
std::string bmp_name = bmp_name_in;
|
||||
boost::replace_last(bmp_name, ".png", "");
|
||||
wxBitmap *bmp = cache.load_svg(bmp_name, height, width);
|
||||
if (bmp == nullptr)
|
||||
bmp = cache.load_png(bmp_name, height, width);
|
||||
return *bmp;
|
||||
}
|
||||
|
||||
void PrusaObjectDataViewModelNode::set_object_action_icon() {
|
||||
|
@ -1239,6 +1243,32 @@ unsigned int PrusaObjectDataViewModel::GetChildren(const wxDataViewItem &parent,
|
|||
return count;
|
||||
}
|
||||
|
||||
void PrusaObjectDataViewModel::GetAllChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const
|
||||
{
|
||||
PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)parent.GetID();
|
||||
if (!node) {
|
||||
for (auto object : m_objects)
|
||||
array.Add(wxDataViewItem((void*)object));
|
||||
}
|
||||
else if (node->GetChildCount() == 0)
|
||||
return;
|
||||
else {
|
||||
const size_t count = node->GetChildren().GetCount();
|
||||
for (size_t pos = 0; pos < count; pos++) {
|
||||
PrusaObjectDataViewModelNode *child = node->GetChildren().Item(pos);
|
||||
array.Add(wxDataViewItem((void*)child));
|
||||
}
|
||||
}
|
||||
|
||||
wxDataViewItemArray new_array = array;
|
||||
for (const auto item : new_array)
|
||||
{
|
||||
wxDataViewItemArray children;
|
||||
GetAllChildren(item, children);
|
||||
WX_APPEND_ARRAY(array, children);
|
||||
}
|
||||
}
|
||||
|
||||
ItemType PrusaObjectDataViewModel::GetItemType(const wxDataViewItem &item) const
|
||||
{
|
||||
if (!item.IsOk())
|
||||
|
@ -1456,8 +1486,8 @@ PrusaDoubleSlider::PrusaDoubleSlider(wxWindow *parent,
|
|||
if (!is_osx)
|
||||
SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
|
||||
|
||||
m_bmp_thumb_higher = wxBitmap(create_scaled_bitmap(style == wxSL_HORIZONTAL ? "right_half_circle.png" : "up_half_circle.png"));
|
||||
m_bmp_thumb_lower = wxBitmap(create_scaled_bitmap(style == wxSL_HORIZONTAL ? "left_half_circle.png" : "down_half_circle.png"));
|
||||
m_bmp_thumb_higher = wxBitmap(style == wxSL_HORIZONTAL ? create_scaled_bitmap("right_half_circle.png") : create_scaled_bitmap("up_half_circle.png", true));
|
||||
m_bmp_thumb_lower = wxBitmap(style == wxSL_HORIZONTAL ? create_scaled_bitmap("left_half_circle.png" ) : create_scaled_bitmap("down_half_circle.png", true));
|
||||
m_thumb_size = m_bmp_thumb_lower.GetSize();
|
||||
|
||||
m_bmp_add_tick_on = create_scaled_bitmap("colorchange_add_on.png");
|
||||
|
|
|
@ -31,7 +31,7 @@ wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxStrin
|
|||
wxMenuItem* append_menu_radio_item(wxMenu* menu, int id, const wxString& string, const wxString& description,
|
||||
std::function<void(wxCommandEvent& event)> cb, wxEvtHandler* event_handler);
|
||||
|
||||
wxBitmap create_scaled_bitmap(const std::string& bmp_name);
|
||||
wxBitmap create_scaled_bitmap(const std::string& bmp_name, const bool is_horizontal = false);
|
||||
|
||||
class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup
|
||||
{
|
||||
|
@ -511,7 +511,7 @@ public:
|
|||
virtual bool IsContainer(const wxDataViewItem &item) const override;
|
||||
virtual unsigned int GetChildren(const wxDataViewItem &parent,
|
||||
wxDataViewItemArray &array) const override;
|
||||
|
||||
void GetAllChildren(const wxDataViewItem &parent,wxDataViewItemArray &array) const;
|
||||
// Is the container just a header or an item with all columns
|
||||
// In our case it is an item with all columns
|
||||
virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue