Merge remote-tracking branch 'origin/master' into ys_search

This commit is contained in:
YuSanka 2020-03-20 14:56:05 +01:00
commit a46a225cf1
83 changed files with 5730 additions and 4609 deletions

5
src/imgui/README.md Normal file
View file

@ -0,0 +1,5 @@
** Dear ImGui is a bloat-free graphical user interface library for C++.**
For more information go to https://github.com/ocornut/imgui
THIS DIRECTORY CONTAINS THE imgui-1.66 da3c433 SOURCE DISTRIBUTION.

View file

@ -43,7 +43,10 @@ protected:
Placer p{bin};
p.configure(pcfg);
if (itm.area() <= 0 || !p.pack(cpy)) it = c.erase(it);
if (itm.area() <= 0 || !p.pack(cpy)) {
static_cast<Item&>(*it).binId(BIN_ID_UNSET);
it = c.erase(it);
}
else it++;
}
}

View file

@ -577,7 +577,7 @@ void _arrange(
std::function<bool()> stopfn)
{
// Integer ceiling the min distance from the bed perimeters
coord_t md = minobjd - 2 * scaled(0.1 + EPSILON);
coord_t md = minobjd;
md = (md % 2) ? md / 2 + 1 : md / 2;
auto corrected_bin = bin;

View file

@ -114,7 +114,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
if (surface.surface_type == stInternalVoid)
has_internal_voids = true;
else {
FlowRole extrusion_role = (surface.surface_type == stTop) ? frTopSolidInfill : (surface.is_solid() ? frSolidInfill : frInfill);
FlowRole extrusion_role = surface.is_top() ? frTopSolidInfill : (surface.is_solid() ? frSolidInfill : frInfill);
bool is_bridge = layer.id() > 0 && surface.is_bridge();
params.extruder = layerm.region()->extruder(extrusion_role);
params.pattern = layerm.region()->config().fill_pattern.value;
@ -132,7 +132,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
is_bridge ?
erBridgeInfill :
(surface.is_solid() ?
((surface.surface_type == stTop) ? erTopSolidInfill : erSolidInfill) :
(surface.is_top() ? erTopSolidInfill : erSolidInfill) :
erInternalInfill);
params.bridge_angle = float(surface.bridge_angle);
params.angle = float(Geometry::deg2rad(layerm.region()->config().fill_angle.value));

View file

@ -2880,11 +2880,12 @@ std::string GCode::extrude_path(ExtrusionPath path, std::string description, dou
std::string GCode::extrude_perimeters(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, std::unique_ptr<EdgeGrid::Grid> &lower_layer_edge_grid)
{
std::string gcode;
for (const ObjectByExtruder::Island::Region &region : by_region) {
m_config.apply(print.regions()[&region - &by_region.front()]->config());
for (const ExtrusionEntity *ee : region.perimeters)
gcode += this->extrude_entity(*ee, "perimeter", -1., &lower_layer_edge_grid);
}
for (const ObjectByExtruder::Island::Region &region : by_region)
if (! region.perimeters.empty()) {
m_config.apply(print.regions()[&region - &by_region.front()]->config());
for (const ExtrusionEntity *ee : region.perimeters)
gcode += this->extrude_entity(*ee, "perimeter", -1., &lower_layer_edge_grid);
}
return gcode;
}
@ -2892,19 +2893,20 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vector<Obje
std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region)
{
std::string gcode;
for (const ObjectByExtruder::Island::Region &region : by_region) {
m_config.apply(print.regions()[&region - &by_region.front()]->config());
ExtrusionEntitiesPtr extrusions { region.infills };
chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
for (const ExtrusionEntity *fill : extrusions) {
auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill);
if (eec) {
for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
gcode += this->extrude_entity(*ee, "infill");
} else
gcode += this->extrude_entity(*fill, "infill");
for (const ObjectByExtruder::Island::Region &region : by_region)
if (! region.infills.empty()) {
m_config.apply(print.regions()[&region - &by_region.front()]->config());
ExtrusionEntitiesPtr extrusions { region.infills };
chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
for (const ExtrusionEntity *fill : extrusions) {
auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill);
if (eec) {
for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
gcode += this->extrude_entity(*ee, "infill");
} else
gcode += this->extrude_entity(*fill, "infill");
}
}
}
return gcode;
}
@ -3370,17 +3372,18 @@ const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtru
has_overrides = true;
break;
}
// Data is cleared, but the memory is not.
by_region_per_copy_cache.clear();
if (! has_overrides)
// Simple case. No need to copy the regions.
return this->by_region;
return wiping_entities ? by_region_per_copy_cache : this->by_region;
// Complex case. Some of the extrusions of some object instances are to be printed first - those are the wiping extrusions.
// Some of the extrusions of some object instances are printed later - those are the clean print extrusions.
// Filter out the extrusions based on the infill_overrides / perimeter_overrides:
// Data is cleared, but the memory is not.
by_region_per_copy_cache.clear();
for (const auto& reg : by_region) {
by_region_per_copy_cache.emplace_back(); // creates a region in the newly created Island
@ -3441,15 +3444,17 @@ void GCode::ObjectByExtruder::Island::Region::append(const Type type, const Extr
// First we append the entities, there are eec->entities.size() of them:
size_t old_size = perimeters_or_infills->size();
perimeters_or_infills->reserve(perimeters_or_infills->size() + eec->entities.size());
size_t new_size = old_size + eec->entities.size();
perimeters_or_infills->reserve(new_size);
for (auto* ee : eec->entities)
perimeters_or_infills->emplace_back(ee);
if (copies_extruder != nullptr) {
perimeters_or_infills_overrides->reserve(old_size + eec->entities.size());
perimeters_or_infills_overrides->resize(old_size, nullptr);
for (unsigned int i = 0; i < eec->entities.size(); ++ i)
perimeters_or_infills_overrides->emplace_back(copies_extruder);
// Don't reallocate overrides if not needed.
// Missing overrides are implicitely considered non-overridden.
perimeters_or_infills_overrides->reserve(new_size);
perimeters_or_infills_overrides->resize(old_size, nullptr);
perimeters_or_infills_overrides->resize(new_size, copies_extruder);
}
}

View file

@ -241,6 +241,7 @@ void GCodePreviewData::reset()
ranges.width.reset();
ranges.height.reset();
ranges.feedrate.reset();
ranges.fan_speed.reset();
ranges.volumetric_rate.reset();
extrusion.layers.clear();
travel.polylines.clear();

View file

@ -112,72 +112,82 @@ void Layer::make_perimeters()
// keep track of regions whose perimeters we have already generated
std::vector<unsigned char> done(m_regions.size(), false);
for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm) {
size_t region_id = layerm - m_regions.begin();
if (done[region_id])
continue;
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id;
done[region_id] = true;
const PrintRegionConfig &config = (*layerm)->region()->config();
// find compatible regions
LayerRegionPtrs layerms;
layerms.push_back(*layerm);
for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it) {
LayerRegion* other_layerm = *it;
const PrintRegionConfig &other_config = other_layerm->region()->config();
if (config.perimeter_extruder == other_config.perimeter_extruder
&& config.perimeters == other_config.perimeters
&& config.perimeter_speed == other_config.perimeter_speed
&& config.external_perimeter_speed == other_config.external_perimeter_speed
&& config.gap_fill_speed == other_config.gap_fill_speed
&& config.overhangs == other_config.overhangs
&& config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width")
&& config.thin_walls == other_config.thin_walls
&& config.external_perimeters_first == other_config.external_perimeters_first
&& config.infill_overlap == other_config.infill_overlap) {
layerms.push_back(other_layerm);
done[it - m_regions.begin()] = true;
}
}
if (layerms.size() == 1) { // optimization
(*layerm)->fill_surfaces.surfaces.clear();
(*layerm)->make_perimeters((*layerm)->slices, &(*layerm)->fill_surfaces);
(*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces);
} else {
SurfaceCollection new_slices;
// Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence.
LayerRegion *layerm_config = layerms.front();
{
// group slices (surfaces) according to number of extra perimeters
std::map<unsigned short, Surfaces> slices; // extra_perimeters => [ surface, surface... ]
for (LayerRegion *layerm : layerms) {
for (Surface &surface : layerm->slices.surfaces)
slices[surface.extra_perimeters].emplace_back(surface);
if (layerm->region()->config().fill_density > layerm_config->region()->config().fill_density)
layerm_config = layerm;
}
// merge the surfaces assigned to each group
for (std::pair<const unsigned short,Surfaces> &surfaces_with_extra_perimeters : slices)
new_slices.append(union_ex(surfaces_with_extra_perimeters.second, true), surfaces_with_extra_perimeters.second.front());
}
// make perimeters
SurfaceCollection fill_surfaces;
layerm_config->make_perimeters(new_slices, &fill_surfaces);
for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm)
if ((*layerm)->slices.empty()) {
(*layerm)->perimeters.clear();
(*layerm)->fills.clear();
(*layerm)->thin_fills.clear();
} else {
size_t region_id = layerm - m_regions.begin();
if (done[region_id])
continue;
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id;
done[region_id] = true;
const PrintRegionConfig &config = (*layerm)->region()->config();
// find compatible regions
LayerRegionPtrs layerms;
layerms.push_back(*layerm);
for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it)
if (! (*it)->slices.empty()) {
LayerRegion* other_layerm = *it;
const PrintRegionConfig &other_config = other_layerm->region()->config();
if (config.perimeter_extruder == other_config.perimeter_extruder
&& config.perimeters == other_config.perimeters
&& config.perimeter_speed == other_config.perimeter_speed
&& config.external_perimeter_speed == other_config.external_perimeter_speed
&& config.gap_fill_speed == other_config.gap_fill_speed
&& config.overhangs == other_config.overhangs
&& config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width")
&& config.thin_walls == other_config.thin_walls
&& config.external_perimeters_first == other_config.external_perimeters_first
&& config.infill_overlap == other_config.infill_overlap)
{
other_layerm->perimeters.clear();
other_layerm->fills.clear();
other_layerm->thin_fills.clear();
layerms.push_back(other_layerm);
done[it - m_regions.begin()] = true;
}
}
if (layerms.size() == 1) { // optimization
(*layerm)->fill_surfaces.surfaces.clear();
(*layerm)->make_perimeters((*layerm)->slices, &(*layerm)->fill_surfaces);
(*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces);
} else {
SurfaceCollection new_slices;
// Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence.
LayerRegion *layerm_config = layerms.front();
{
// group slices (surfaces) according to number of extra perimeters
std::map<unsigned short, Surfaces> slices; // extra_perimeters => [ surface, surface... ]
for (LayerRegion *layerm : layerms) {
for (Surface &surface : layerm->slices.surfaces)
slices[surface.extra_perimeters].emplace_back(surface);
if (layerm->region()->config().fill_density > layerm_config->region()->config().fill_density)
layerm_config = layerm;
}
// merge the surfaces assigned to each group
for (std::pair<const unsigned short,Surfaces> &surfaces_with_extra_perimeters : slices)
new_slices.append(union_ex(surfaces_with_extra_perimeters.second, true), surfaces_with_extra_perimeters.second.front());
}
// make perimeters
SurfaceCollection fill_surfaces;
layerm_config->make_perimeters(new_slices, &fill_surfaces);
// assign fill_surfaces to each layer
if (!fill_surfaces.surfaces.empty()) {
for (LayerRegionPtrs::iterator l = layerms.begin(); l != layerms.end(); ++l) {
// Separate the fill surfaces.
ExPolygons expp = intersection_ex(to_polygons(fill_surfaces), (*l)->slices);
(*l)->fill_expolygons = expp;
(*l)->fill_surfaces.set(std::move(expp), fill_surfaces.surfaces.front());
}
}
}
}
// assign fill_surfaces to each layer
if (!fill_surfaces.surfaces.empty()) {
for (LayerRegionPtrs::iterator l = layerms.begin(); l != layerms.end(); ++l) {
// Separate the fill surfaces.
ExPolygons expp = intersection_ex(to_polygons(fill_surfaces), (*l)->slices);
(*l)->fill_expolygons = expp;
(*l)->fill_surfaces.set(std::move(expp), fill_surfaces.surfaces.front());
}
}
}
}
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done";
}

View file

@ -117,7 +117,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
// Voids are sparse infills if infill rate is zero.
Polygons voids;
for (const Surface &surface : this->fill_surfaces.surfaces) {
if (surface.surface_type == stTop) {
if (surface.is_top()) {
// Collect the top surfaces, inflate them and trim them by the bottom surfaces.
// This gives the priority to bottom surfaces.
surfaces_append(top, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
@ -313,7 +313,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
s2.clear();
}
}
if (s1.surface_type == stTop)
if (s1.is_top())
// Trim the top surfaces by the bottom surfaces. This gives the priority to the bottom surfaces.
polys = diff(polys, bottom_polygons);
surfaces_append(

View file

@ -161,6 +161,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
} else if (
opt_key == "skirts"
|| opt_key == "skirt_height"
|| opt_key == "draft_shield"
|| opt_key == "skirt_distance"
|| opt_key == "min_skirt_length"
|| opt_key == "ooze_prevention"
@ -1146,14 +1147,12 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
bool Print::has_infinite_skirt() const
{
return (m_config.skirt_height == -1 && m_config.skirts > 0)
|| (m_config.ooze_prevention && this->extruders().size() > 1);
return (m_config.draft_shield && m_config.skirts > 0) || (m_config.ooze_prevention && this->extruders().size() > 1);
}
bool Print::has_skirt() const
{
return (m_config.skirt_height > 0 && m_config.skirts > 0)
|| this->has_infinite_skirt();
return (m_config.skirt_height > 0 && m_config.skirts > 0) || this->has_infinite_skirt();
}
static inline bool sequential_print_horizontal_clearance_valid(const Print &print)

View file

@ -1691,6 +1691,13 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(1));
def = this->add("draft_shield", coBool);
def->label = L("Draft shield");
def->tooltip = L("If enabled, the skirt will be as tall as a highest printed object. "
"This is useful to protect an ABS or ASA print from warping and detaching from print bed due to wind draft.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("skirts", coInt);
def->label = L("Loops (minimum)");
def->full_label = L("Skirt Loops");
@ -2998,6 +3005,11 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
} else if (opt_key == "support_material_pattern" && value == "pillars") {
// Slic3r PE does not support the pillars. They never worked well.
value = "rectilinear";
} else if (opt_key == "skirt_height" && value == "-1") {
// PrusaSlicer no more accepts skirt_height == -1 to print a draft shield to the top of the highest object.
// A new "draft_shield" boolean config value is used instead.
opt_key = "draft_shield";
value = "1";
} else if (opt_key == "octoprint_host") {
opt_key = "print_host";
} else if (opt_key == "octoprint_cafile") {
@ -3206,7 +3218,7 @@ std::string FullPrintConfig::validate()
return "Invalid value for --infill-every-layers";
// --skirt-height
if (this->skirt_height < -1) // -1 means as tall as the object
if (this->skirt_height < 0)
return "Invalid value for --skirt-height";
// --bridge-flow-ratio

View file

@ -800,6 +800,7 @@ public:
ConfigOptionBools retract_layer_change;
ConfigOptionFloat skirt_distance;
ConfigOptionInt skirt_height;
ConfigOptionBool draft_shield;
ConfigOptionInt skirts;
ConfigOptionInts slowdown_below_layer_time;
ConfigOptionBool spiral_vase;
@ -872,6 +873,7 @@ protected:
OPT_PTR(retract_layer_change);
OPT_PTR(skirt_distance);
OPT_PTR(skirt_height);
OPT_PTR(draft_shield);
OPT_PTR(skirts);
OPT_PTR(slowdown_below_layer_time);
OPT_PTR(spiral_vase);

View file

@ -817,11 +817,12 @@ void PrintObject::detect_surfaces_type()
m_layers[idx_layer]->m_regions[idx_region]->slices.surfaces = std::move(surfaces_new[idx_layer]);
}
if (spiral_vase && num_layers > 1) {
// Turn the last bottom layer infill to a top infill, so it will be extruded with a proper pattern.
Surfaces &surfaces = m_layers[num_layers - 1]->m_regions[idx_region]->slices.surfaces;
for (Surface &surface : surfaces)
surface.surface_type = stTop;
if (spiral_vase) {
if (num_layers > 1)
// Turn the last bottom layer infill to a top infill, so it will be extruded with a proper pattern.
m_layers[num_layers - 1]->m_regions[idx_region]->slices.set_type(stTop);
for (size_t i = num_layers; i < m_layers.size(); ++ i)
m_layers[i]->m_regions[idx_region]->slices.set_type(stInternal);
}
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " - clipping in parallel - start";

View file

@ -34,6 +34,10 @@ public:
void remove_type(const SurfaceType type);
void remove_types(const SurfaceType *types, int ntypes);
void filter_by_type(SurfaceType type, Polygons* polygons);
void set_type(SurfaceType type) {
for (Surface &surface : this->surfaces)
surface.surface_type = type;
}
void clear() { surfaces.clear(); }
bool empty() const { return surfaces.empty(); }

View file

@ -42,12 +42,42 @@
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG (0 && ENABLE_THUMBNAIL_GENERATOR)
//==================
//================
// 2.2.0.rc1 techs
//==================
//================
#define ENABLE_2_2_0_RC1 1
// Enable hack to remove crash when closing on OSX 10.9.5
#define ENABLE_HACK_CLOSING_ON_OSX_10_9_5 (1 && ENABLE_2_2_0_RC1)
//============
// 2.2.0 techs
//============
#define ENABLE_2_2_0 1
// Enable automatic switch to constrained camera when manipulating the scene using regular mouse
// while 3D mouse is connected and free camera is not selected
#define ENABLE_AUTO_CONSTRAINED_CAMERA (1 && ENABLE_2_2_0)
//==================
// 2.2.0.final techs
//==================
#define ENABLE_2_2_0_FINAL 1
// Enable tooltips for GLCanvas3D using ImGUI
#define ENABLE_CANVAS_TOOLTIP_USING_IMGUI (1 && ENABLE_2_2_0_FINAL)
// Enable constraining tooltips for GLCanvas3D using ImGUI into canvas area
#define ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI (1 && ENABLE_CANVAS_TOOLTIP_USING_IMGUI)
// Enable delay for showing tooltips for GLCanvas3D using ImGUI
#define ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI (1 && ENABLE_CANVAS_TOOLTIP_USING_IMGUI)
// Enable modified mouse events handling for toolbars
#define ENABLE_MODIFIED_TOOLBAR_MOUSE_EVENT_HANDLING (1 && ENABLE_CANVAS_TOOLTIP_USING_IMGUI)
// Enable modified mouse events handling for gizmobar
#define ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING (1 && ENABLE_CANVAS_TOOLTIP_USING_IMGUI)
// Enable fix for dragging mouse event handling for gizmobar
#define ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX (1 && ENABLE_2_2_0_FINAL)
#endif // _technologies_h_

View file

@ -268,8 +268,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
"bridge_acceleration", "first_layer_acceleration" })
toggle_field(el, have_default_acceleration);
bool have_skirt = config->opt_int("skirts") > 0 || config->opt_float("min_skirt_length") > 0;
for (auto el : { "skirt_distance", "skirt_height" })
bool have_skirt = config->opt_int("skirts") > 0;
toggle_field("skirt_height", have_skirt && !config->opt_bool("draft_shield"));
for (auto el : { "skirt_distance", "draft_shield", "min_skirt_length" })
toggle_field(el, have_skirt);
bool have_brim = config->opt_float("brim_width") > 0;

View file

@ -450,7 +450,7 @@ PageWelcome::PageWelcome(ConfigWizard *parent)
% _utf8(ConfigWizard::name())).str())
))
, cbox_reset(append(
new wxCheckBox(this, wxID_ANY, _(L("Remove user profiles - install from scratch (a snapshot will be taken beforehand)")))
new wxCheckBox(this, wxID_ANY, _(L("Remove user profiles (a snapshot will be taken beforehand)")))
))
{
welcome_text->Hide();
@ -1473,12 +1473,41 @@ void ConfigWizard::priv::load_vendors()
pair.second.preset_bundle->load_installed_printers(appconfig_new);
}
if (app_config->has_section(AppConfig::SECTION_FILAMENTS)) {
appconfig_new.set_section(AppConfig::SECTION_FILAMENTS, app_config->get_section(AppConfig::SECTION_FILAMENTS));
}
if (app_config->has_section(AppConfig::SECTION_MATERIALS)) {
appconfig_new.set_section(AppConfig::SECTION_MATERIALS, app_config->get_section(AppConfig::SECTION_MATERIALS));
}
// Copy installed filaments and SLA material names from app_config to appconfig_new
// while resolving current names of profiles, which were renamed in the meantime.
for (PrinterTechnology technology : { ptFFF, ptSLA }) {
const std::string &section_name = (technology == ptFFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
std::map<std::string, std::string> section_new;
if (app_config->has_section(section_name)) {
const std::map<std::string, std::string> &section_old = app_config->get_section(section_name);
for (const std::pair<std::string, std::string> &material_name_and_installed : section_old)
if (material_name_and_installed.second == "1") {
// Material is installed. Resolve it in bundles.
size_t num_found = 0;
const std::string &material_name = material_name_and_installed.first;
for (auto &bundle : bundles) {
const PresetCollection &materials = bundle.second.preset_bundle->materials(technology);
const Preset *preset = materials.find_preset(material_name);
if (preset == nullptr) {
// Not found. Maybe the material preset is there, bu it was was renamed?
const std::string *new_name = materials.get_preset_name_renamed(material_name);
if (new_name != nullptr)
preset = materials.find_preset(*new_name);
}
if (preset != nullptr) {
// Materal preset was found, mark it as installed.
section_new[preset->name] = "1";
++ num_found;
}
}
if (num_found == 0)
BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was not found in installed vendor Preset Bundles.") % material_name;
else if (num_found > 1)
BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was found in %2% vendor Preset Bundles.") % material_name % num_found;
}
}
appconfig_new.set_section(section_name, section_new);
};
}
void ConfigWizard::priv::add_page(ConfigWizardPage *page)
@ -1642,9 +1671,9 @@ void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPicker
}
}
// if at list one printer is selected but there in no one selected material,
// select materials which is default for selected printer(s)
select_default_materials_if_needed(pair.second.vendor_profile, page->technology, evt.model_id);
// When a printer model is picked, but there is no material installed compatible with this printer model,
// install default materials for selected printer model silently.
check_and_install_missing_materials(page->technology, evt.model_id);
}
if (page->technology & T_FFF) {
@ -1654,41 +1683,26 @@ void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPicker
}
}
void ConfigWizard::priv::select_default_materials_for_printer_model(const std::vector<VendorProfile::PrinterModel>& models, Technology technology, const std::string& model_id)
void ConfigWizard::priv::select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology)
{
PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials;
auto it = std::find_if(models.begin(), models.end(), [model_id](VendorProfile::PrinterModel model) {return model_id == model.id; });
if (it != models.end())
for (const std::string& material : it->default_materials)
appconfig_new.set(page_materials->materials->appconfig_section(), material, "1");
for (const std::string& material : printer_model.default_materials)
appconfig_new.set(page_materials->materials->appconfig_section(), material, "1");
}
void ConfigWizard::priv::select_default_materials_if_needed(VendorProfile* vendor_profile, Technology technology, const std::string& model_id)
void ConfigWizard::priv::select_default_materials_for_printer_models(Technology technology, const std::set<const VendorProfile::PrinterModel*> &printer_models)
{
if ((technology & T_FFF && !any_fff_selected) ||
(technology & T_SLA && !any_sla_selected) ||
check_materials_in_config(technology, false))
return;
PageMaterials *page_materials = technology & T_FFF ? page_filaments : page_sla_materials;
const std::string &appconfig_section = page_materials->materials->appconfig_section();
select_default_materials_for_printer_model(vendor_profile->models, technology, model_id);
}
void ConfigWizard::priv::selected_default_materials(Technology technology)
{
auto select_default_materials_for_printer_page = [this](PagePrinters * page_printers, Technology technology)
auto select_default_materials_for_printer_page = [this, appconfig_section, printer_models](PagePrinters *page_printers, Technology technology)
{
std::set<std::string> selected_models = page_printers->get_selected_models();
const std::string vendor_id = page_printers->get_vendor_id();
const std::string vendor_id = page_printers->get_vendor_id();
for (auto& pair : bundles)
{
if (pair.first != vendor_id)
continue;
for (const std::string& model_id : selected_models)
select_default_materials_for_printer_model(pair.second.vendor_profile->models, technology, model_id);
}
if (pair.first == vendor_id)
for (const VendorProfile::PrinterModel *printer_model : printer_models)
for (const std::string &material : printer_model->default_materials)
appconfig_new.set(appconfig_section, material, "1");
};
PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla;
@ -1702,7 +1716,7 @@ void ConfigWizard::priv::selected_default_materials(Technology technology)
}
update_materials(technology);
(technology& T_FFF ? page_filaments : page_sla_materials)->reload_presets();
((technology & T_FFF) ? page_filaments : page_sla_materials)->reload_presets();
}
void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install)
@ -1743,51 +1757,105 @@ bool ConfigWizard::priv::on_bnt_finish()
// theres no need to check that filament is selected if we have only custom printer
if (custom_printer_selected && !any_fff_selected && !any_sla_selected) return true;
// check, that there is selected at least one filament/material
return check_materials_in_config(T_ANY);
return check_and_install_missing_materials(T_ANY);
}
bool ConfigWizard::priv::check_materials_in_config(Technology technology, bool show_info_msg)
// This allmighty method verifies, whether there is at least a single compatible filament or SLA material installed
// for each Printer preset of each Printer Model installed.
//
// In case only_for_model_id is set, then the test is done for that particular printer model only, and the default materials are installed silently.
// Otherwise the user is quieried whether to install the missing default materials or not.
//
// Return true if the tested Printer Models already had materials installed.
// Return false if there were some Printer Models with missing materials, independent from whether the defaults were installed for these
// respective Printer Models or not.
bool ConfigWizard::priv::check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id)
{
const auto exist_preset = [this](const std::string& section, const Materials& materials)
// Walk over all installed Printer presets and verify whether there is a filament or SLA material profile installed at the same PresetBundle,
// which is compatible with it.
const auto printer_models_missing_materials = [this, only_for_model_id](PrinterTechnology technology, const std::string &section)
{
if (appconfig_new.has_section(section) &&
!appconfig_new.get_section(section).empty())
{
const std::map<std::string, std::string>& appconfig_presets = appconfig_new.get_section(section);
for (const auto& preset : appconfig_presets)
if (materials.exist_preset(preset.first))
return true;
const std::map<std::string, std::string> &appconfig_presets = appconfig_new.has_section(section) ? appconfig_new.get_section(section) : std::map<std::string, std::string>();
std::set<const VendorProfile::PrinterModel*> printer_models_without_material;
for (const auto &pair : bundles) {
const PresetCollection &materials = pair.second.preset_bundle->materials(technology);
for (const auto &printer : pair.second.preset_bundle->printers) {
if (printer.is_visible && printer.printer_technology() == technology) {
const VendorProfile::PrinterModel *printer_model = PresetUtils::system_printer_model(printer);
assert(printer_model != nullptr);
if ((only_for_model_id.empty() || only_for_model_id == printer_model->id) &&
printer_models_without_material.find(printer_model) == printer_models_without_material.end()) {
bool has_material = false;
for (const std::pair<std::string, std::string> &preset : appconfig_presets) {
if (preset.second == "1") {
const Preset *material = materials.find_preset(preset.first, false);
if (material != nullptr && is_compatible_with_printer(PresetWithVendorProfile(*material, nullptr), PresetWithVendorProfile(printer, nullptr))) {
has_material = true;
break;
}
}
}
if (! has_material)
printer_models_without_material.insert(printer_model);
}
}
}
}
return false;
assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id);
return printer_models_without_material;
};
const auto ask_and_selected_default_materials = [this](wxString message, Technology technology)
const auto ask_and_select_default_materials = [this](const wxString &message, const std::set<const VendorProfile::PrinterModel*> &printer_models, Technology technology)
{
wxMessageDialog msg(q, message, _(L("Notice")), wxYES_NO);
if (msg.ShowModal() == wxID_YES)
selected_default_materials(technology);
select_default_materials_for_printer_models(technology, printer_models);
};
if (any_fff_selected && technology & T_FFF && !exist_preset(AppConfig::SECTION_FILAMENTS, filaments))
{
if (show_info_msg)
{
wxString message = _(L("You have to select at least one filament for selected printers")) + "\n\n\t" +
_(L("Do you want to automatic select default filaments?"));
ask_and_selected_default_materials(message, T_FFF);
const auto printer_model_list = [](const std::set<const VendorProfile::PrinterModel*> &printer_models) -> wxString {
wxString out;
for (const VendorProfile::PrinterModel *printer_model : printer_models) {
out += "\t\t";
out += from_u8(printer_model->name);
out += "\n";
}
return out;
};
if (any_fff_selected && (technology & T_FFF)) {
std::set<const VendorProfile::PrinterModel*> printer_models_without_material = printer_models_missing_materials(ptFFF, AppConfig::SECTION_FILAMENTS);
if (! printer_models_without_material.empty()) {
if (only_for_model_id.empty())
ask_and_select_default_materials(
_L("The following FFF printer models have no filament selected:") +
"\n\n\t" +
printer_model_list(printer_models_without_material) +
"\n\n\t" +
_L("Do you want to select default filaments for these FFF printer models?"),
printer_models_without_material,
T_FFF);
else
select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_FFF);
return false;
}
return false;
}
if (any_sla_selected && technology & T_SLA && !exist_preset(AppConfig::SECTION_MATERIALS, sla_materials))
{
if (show_info_msg)
{
wxString message = _(L("You have to select at least one material for selected printers")) + "\n\n\t" +
_(L("Do you want to automatic select default materials?"));
ask_and_selected_default_materials(message, T_SLA);
}
return false;
if (any_sla_selected && (technology & T_SLA)) {
std::set<const VendorProfile::PrinterModel*> printer_models_without_material = printer_models_missing_materials(ptSLA, AppConfig::SECTION_MATERIALS);
if (! printer_models_without_material.empty()) {
if (only_for_model_id.empty())
ask_and_select_default_materials(
_L("The following SLA printer models have no materials selected:") +
"\n\n\t" +
printer_model_list(printer_models_without_material) +
"\n\n\t" +
_L("Do you want to select default SLA materials for these printer models?"),
printer_models_without_material,
T_SLA);
else
select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_SLA);
return false;
}
}
return true;
@ -2062,8 +2130,11 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
{
// check, that there is selected at least one filament/material
ConfigWizardPage* active_page = this->p->index->active_page();
if ( (active_page == p->page_filaments || active_page == p->page_sla_materials)
&& !p->check_materials_in_config(dynamic_cast<PageMaterials*>(active_page)->materials->technology))
if (// Leaving the filaments or SLA materials page and
(active_page == p->page_filaments || active_page == p->page_sla_materials) &&
// some Printer models had no filament or SLA material selected.
! p->check_and_install_missing_materials(dynamic_cast<PageMaterials*>(active_page)->materials->technology))
// In that case don't leave the page and the function above queried the user whether to install default materials.
return;
this->p->index->go_next();
});

View file

@ -82,14 +82,6 @@ struct Materials
}
}
bool exist_preset(const std::string& preset_name) const
{
for (const Preset* preset : presets)
if (preset->name == preset_name)
return true;
return false;
}
static const std::string UNKNOWN;
static const std::string& get_filament_type(const Preset *preset);
static const std::string& get_filament_vendor(const Preset *preset);
@ -503,17 +495,12 @@ struct ConfigWizard::priv
void on_custom_setup(const bool custom_wanted);
void on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt);
void select_default_materials_for_printer_model(const std::vector<VendorProfile::PrinterModel> &models,
Technology technology,
const std::string & model_id);
void select_default_materials_if_needed(VendorProfile* vendor_profile,
Technology technology,
const std::string &model_id);
void selected_default_materials(Technology technology);
void select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology);
void select_default_materials_for_printer_models(Technology technology, const std::set<const VendorProfile::PrinterModel*> &printer_models);
void on_3rdparty_install(const VendorProfile *vendor, bool install);
bool on_bnt_finish();
bool check_materials_in_config(Technology technology, bool show_info_msg = true);
bool check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id = std::string());
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
// #ys_FIXME_alise
void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);

View file

@ -61,9 +61,11 @@
#include <algorithm>
#include <cmath>
#include "DoubleSlider.hpp"
#if !ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
#if ENABLE_RENDER_STATISTICS
#include <chrono>
#endif // ENABLE_RENDER_STATISTICS
#endif // !ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
#include <imgui/imgui_internal.h>
@ -663,7 +665,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool
if (it != m_warnings.end()) // this warning is already set to be shown
return;
m_warnings.push_back(warning);
m_warnings.emplace_back(warning);
std::sort(m_warnings.begin(), m_warnings.end());
}
else {
@ -1289,7 +1291,7 @@ void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_
if (model_object->instances.size() > 1)
owner.label += " (" + std::to_string(inst_idx + 1) + ")";
owner.selected = volume->selected;
owners.push_back(owner);
owners.emplace_back(owner);
}
}
}
@ -1370,6 +1372,88 @@ void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_
}
}
#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI
#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
void GLCanvas3D::Tooltip::set_text(const std::string& text)
{
// If the mouse is inside an ImGUI dialog, then the tooltip is suppressed.
const std::string &new_text = m_in_imgui ? std::string() : text;
if (m_text != new_text)
{
if (m_text.empty())
m_start_time = std::chrono::steady_clock::now();
m_text = new_text;
}
}
#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas) const
#else
void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position) const
#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
{
#if ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI
static ImVec2 size(0.0f, 0.0f);
auto validate_position = [](const Vec2d& position, const GLCanvas3D& canvas, const ImVec2& wnd_size) {
Size cnv_size = canvas.get_canvas_size();
float x = std::clamp((float)position(0), 0.0f, (float)cnv_size.get_width() - wnd_size.x);
float y = std::clamp((float)position(1) + 16, 0.0f, (float)cnv_size.get_height() - wnd_size.y);
return Vec2f(x, y);
};
#endif // ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI
#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
if (m_text.empty())
return;
// draw the tooltip as hidden until the delay is expired
float alpha = (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_start_time).count() < 500) ? 0.0f : 1.0;
#else
if (m_text.empty())
return;
#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
#if ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI
Vec2f position = validate_position(mouse_position, canvas, size);
#endif // ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI
ImGuiWrapper& imgui = *wxGetApp().imgui();
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
#if ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI
imgui.set_next_window_pos(position(0), position(1), ImGuiCond_Always, 0.0f, 0.0f);
#else
imgui.set_next_window_pos(mouse_position(0), mouse_position(1) + 16, ImGuiCond_Always, 0.0f, 0.0f);
#endif // ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI
imgui.begin(_(L("canvas_tooltip")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing);
ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow());
ImGui::TextUnformatted(m_text.c_str());
#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
// force re-render while the windows gets to its final size (it may take several frames) or while hidden
if (alpha == 0.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowExpectedSize(ImGui::GetCurrentWindow()).x)
canvas.request_extra_frame();
#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
#if ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI
size = ImGui::GetWindowSize();
#endif // ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI
imgui.end();
#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
ImGui::PopStyleVar(2);
#else
ImGui::PopStyleVar();
#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
}
#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI
wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent);
@ -1941,6 +2025,38 @@ void GLCanvas3D::render()
m_camera.debug_render();
#endif // ENABLE_CAMERA_STATISTICS
#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI
std::string tooltip;
// Negative coordinate means out of the window, likely because the window was deactivated.
// In that case the tooltip should be hidden.
if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.)
{
if (tooltip.empty())
tooltip = m_layers_editing.get_tooltip(*this);
if (tooltip.empty())
tooltip = m_gizmos.get_tooltip();
if (tooltip.empty())
tooltip = m_main_toolbar.get_tooltip();
if (tooltip.empty())
tooltip = m_undoredo_toolbar.get_tooltip();
if (tooltip.empty())
tooltip = m_view_toolbar.get_tooltip();
}
set_tooltip(tooltip);
#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
m_tooltip.render(m_mouse.position, *this);
#else
m_tooltip.render(m_mouse.position);
#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI
wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this);
wxGetApp().imgui()->render();
@ -1951,6 +2067,27 @@ void GLCanvas3D::render()
auto end_time = std::chrono::high_resolution_clock::now();
m_render_stats.last_frame = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count();
#endif // ENABLE_RENDER_STATISTICS
#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI
std::string tooltip = "";
if (tooltip.empty())
tooltip = m_layers_editing.get_tooltip(*this);
if (tooltip.empty())
tooltip = m_gizmos.get_tooltip();
if (tooltip.empty())
tooltip = m_main_toolbar.get_tooltip();
if (tooltip.empty())
tooltip = m_undoredo_toolbar.get_tooltip();
if (tooltip.empty())
tooltip = m_view_toolbar.get_tooltip();
set_tooltip(tooltip);
#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI
}
#if ENABLE_THUMBNAIL_GENERATOR
@ -2029,7 +2166,7 @@ std::vector<int> GLCanvas3D::load_object(const ModelObject& model_object, int ob
{
for (unsigned int i = 0; i < model_object.instances.size(); ++i)
{
instance_idxs.push_back(i);
instance_idxs.emplace_back(i);
}
}
return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_initialized);
@ -2469,9 +2606,9 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio
for (const GCodePreviewData::Retraction::Position& position : copy)
{
volume->print_zs.push_back(unscale<double>(position.position(2)));
volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size());
volume->print_zs.emplace_back(unscale<double>(position.position(2)));
volume->offsets.emplace_back(volume->indexed_vertex_array.quad_indices.size());
volume->offsets.emplace_back(volume->indexed_vertex_array.triangle_indices.size());
_3DScene::point3_to_verts(position.position, position.width, position.height, *volume);
@ -3200,16 +3337,24 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
evt.SetY(evt.GetY() * scale);
#endif
Point pos(evt.GetX(), evt.GetY());
Point pos(evt.GetX(), evt.GetY());
ImGuiWrapper *imgui = wxGetApp().imgui();
ImGuiWrapper* imgui = wxGetApp().imgui();
m_tooltip.set_in_imgui(false);
if (imgui->update_mouse_data(evt)) {
m_mouse.position = evt.Leaving() ? Vec2d(-1.0, -1.0) : pos.cast<double>();
m_tooltip.set_in_imgui(true);
render();
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
return;
// do not return if dragging or tooltip not empty to allow for tooltip update
#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI
if (!m_mouse.dragging && m_tooltip.is_empty())
#else
if (!m_mouse.dragging && m_canvas->GetToolTipText().empty())
#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI
return;
}
#ifdef __WXMSW__
@ -3260,6 +3405,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
mouse_up_cleanup();
m_mouse.set_start_position_3D_as_invalid();
#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI
m_mouse.position = pos.cast<double>();
#endif /// ENABLE_CANVAS_TOOLTIP_USING_IMGUI
return;
}
@ -3466,11 +3614,26 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (m_hover_volume_idxs.empty() && m_mouse.is_start_position_3D_defined())
{
const Vec3d rot = (Vec3d(pos.x(), pos.y(), 0.) - m_mouse.drag.start_position_3D) * (PI * TRACKBALLSIZE / 180.);
#if ENABLE_AUTO_CONSTRAINED_CAMERA
if (wxGetApp().app_config->get("use_free_camera") == "1")
// Virtual track ball (similar to the 3DConnexion mouse).
m_camera.rotate_local_around_target(Vec3d(rot.y(), rot.x(), 0.));
else
{
// Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation.
// It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(),
// which checks an atomics (flushes CPU caches).
// See GH issue #3816.
m_camera.recover_from_free_camera();
m_camera.rotate_on_sphere(rot.x(), rot.y(), wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA);
}
#else
if (wxGetApp().plater()->get_mouse3d_controller().connected() || (wxGetApp().app_config->get("use_free_camera") == "1"))
// Virtual track ball (similar to the 3DConnexion mouse).
m_camera.rotate_local_around_target(Vec3d(rot.y(), rot.x(), 0.));
else
m_camera.rotate_on_sphere(rot.x(), rot.y(), wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA);
#endif // ENABLE_AUTO_CONSTRAINED_CAMERA
m_dirty = true;
}
@ -3485,6 +3648,15 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
float z = 0.0f;
const Vec3d& cur_pos = _mouse_to_3d(pos, &z);
Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z);
#if ENABLE_AUTO_CONSTRAINED_CAMERA
if (wxGetApp().app_config->get("use_free_camera") != "1")
// Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation.
// It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(),
// which checks an atomics (flushes CPU caches).
// See GH issue #3816.
m_camera.recover_from_free_camera();
#endif // ENABLE_AUTO_CONSTRAINED_CAMERA
m_camera.set_target(m_camera.get_target() + orig - cur_pos);
m_dirty = true;
}
@ -3563,24 +3735,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
else if (evt.Moving())
{
m_mouse.position = pos.cast<double>();
std::string tooltip = "";
if (tooltip.empty())
tooltip = m_layers_editing.get_tooltip(*this);
if (tooltip.empty())
tooltip = m_gizmos.get_tooltip();
if (tooltip.empty())
tooltip = m_main_toolbar.get_tooltip();
if (tooltip.empty())
tooltip = m_undoredo_toolbar.get_tooltip();
if (tooltip.empty())
tooltip = m_view_toolbar.get_tooltip();
set_tooltip(tooltip);
// updates gizmos overlay
if (m_selection.is_empty())
@ -3655,20 +3809,27 @@ void GLCanvas3D::set_tooltip(const std::string& tooltip) const
{
if (m_canvas != nullptr)
{
wxToolTip* t = m_canvas->GetToolTip();
if (t != nullptr)
{
if (tooltip.empty())
m_canvas->UnsetToolTip();
else
t->SetTip(wxString::FromUTF8(tooltip.data()));
}
else if (!tooltip.empty()) // Avoid "empty" tooltips => unset of the empty tooltip leads to application crash under OSX
m_canvas->SetToolTip(wxString::FromUTF8(tooltip.data()));
#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI
m_tooltip.set_text(tooltip);
#else
wxString txt = wxString::FromUTF8(tooltip.data());
if (m_canvas->GetToolTipText() != txt)
m_canvas->SetToolTip(txt);
// wxToolTip* t = m_canvas->GetToolTip();
// if (t != nullptr)
// {
// if (tooltip.empty())
// m_canvas->UnsetToolTip();
// else
// t->SetTip(wxString::FromUTF8(tooltip.data()));
// }
// else if (!tooltip.empty()) // Avoid "empty" tooltips => unset of the empty tooltip leads to application crash under OSX
// m_canvas->SetToolTip(wxString::FromUTF8(tooltip.data()));
#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI
}
}
void GLCanvas3D::do_move(const std::string& snapshot_type)
{
if (m_model == nullptr)
@ -4109,7 +4270,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
if (!vol->is_modifier && !vol->is_wipe_tower && (!parts_only || (vol->composite_id.volume_id >= 0)))
{
if (!printable_only || is_visible(*vol))
visible_volumes.push_back(vol);
visible_volumes.emplace_back(vol);
}
}
@ -4813,7 +4974,7 @@ void GLCanvas3D::_picking_pass() const
}
if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size()))
{
m_hover_volume_idxs.push_back(volume_id);
m_hover_volume_idxs.emplace_back(volume_id);
m_gizmos.set_hover_id(-1);
}
else
@ -5057,7 +5218,7 @@ void GLCanvas3D::_render_overlays() const
if (sequential_print) {
for (ModelObject* model_object : m_model->objects)
for (ModelInstance* model_instance : model_object->instances) {
sorted_instances.push_back(model_instance);
sorted_instances.emplace_back(model_instance);
}
}
m_labels.render(sorted_instances);
@ -5515,29 +5676,26 @@ void GLCanvas3D::_load_print_toolpaths()
if ((skirt_height == 0) && (print->config().brim_width.value > 0))
skirt_height = 1;
// get first skirt_height layers (maybe this should be moved to a PrintObject method?)
const PrintObject* object0 = print->objects().front();
// Get first skirt_height layers.
//FIXME This code is fishy. It may not work for multiple objects with different layering due to variable layer height feature.
// This is not critical as this is just an initial preview.
const PrintObject* highest_object = *std::max_element(print->objects().begin(), print->objects().end(), [](auto l, auto r){ return l->layers().size() < r->layers().size(); });
std::vector<float> print_zs;
print_zs.reserve(skirt_height * 2);
for (size_t i = 0; i < std::min(skirt_height, object0->layers().size()); ++i)
{
print_zs.push_back(float(object0->layers()[i]->print_z));
}
//FIXME why there are support layers?
for (size_t i = 0; i < std::min(skirt_height, object0->support_layers().size()); ++i)
{
print_zs.push_back(float(object0->support_layers()[i]->print_z));
}
for (size_t i = 0; i < std::min(skirt_height, highest_object->layers().size()); ++ i)
print_zs.emplace_back(float(highest_object->layers()[i]->print_z));
// Only add skirt for the raft layers.
for (size_t i = 0; i < std::min(skirt_height, std::min(highest_object->slicing_parameters().raft_layers(), highest_object->support_layers().size())); ++ i)
print_zs.emplace_back(float(highest_object->support_layers()[i]->print_z));
sort_remove_duplicates(print_zs);
if (print_zs.size() > skirt_height)
print_zs.erase(print_zs.begin() + skirt_height, print_zs.end());
skirt_height = std::min(skirt_height, print_zs.size());
print_zs.erase(print_zs.begin() + skirt_height, print_zs.end());
GLVolume *volume = m_volumes.new_toolpath_volume(color, VERTEX_BUFFER_RESERVE_SIZE);
for (size_t i = 0; i < skirt_height; ++i) {
volume->print_zs.push_back(print_zs[i]);
volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size());
for (size_t i = 0; i < skirt_height; ++ i) {
volume->print_zs.emplace_back(print_zs[i]);
volume->offsets.emplace_back(volume->indexed_vertex_array.quad_indices.size());
volume->offsets.emplace_back(volume->indexed_vertex_array.triangle_indices.size());
if (i == 0)
_3DScene::extrusionentity_to_verts(print->brim(), print_zs[i], Point(0, 0), *volume);
_3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), *volume);
@ -5703,10 +5861,10 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
}
if (ctxt.has_perimeters || ctxt.has_infill)
for (const Layer *layer : print_object.layers())
ctxt.layers.push_back(layer);
ctxt.layers.emplace_back(layer);
if (ctxt.has_support)
for (const Layer *layer : print_object.support_layers())
ctxt.layers.push_back(layer);
ctxt.layers.emplace_back(layer);
std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; });
// Maximum size of an allocation block: 32MB / sizeof(float)
@ -5775,9 +5933,9 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
for (GLVolume *vol : vols)
if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) {
vol->print_zs.push_back(layer->print_z);
vol->offsets.push_back(vol->indexed_vertex_array.quad_indices.size());
vol->offsets.push_back(vol->indexed_vertex_array.triangle_indices.size());
vol->print_zs.emplace_back(layer->print_z);
vol->offsets.emplace_back(vol->indexed_vertex_array.quad_indices.size());
vol->offsets.emplace_back(vol->indexed_vertex_array.triangle_indices.size());
}
for (const PrintInstance &instance : *ctxt.shifted_copies) {
const Point &copy = instance.shift;
@ -5933,9 +6091,9 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_
for (size_t i = 0; i < vols.size(); ++i) {
GLVolume &vol = *vols[i];
if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) {
vol.print_zs.push_back(layer.front().print_z);
vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
vol.print_zs.emplace_back(layer.front().print_z);
vol.offsets.emplace_back(vol.indexed_vertex_array.quad_indices.size());
vol.offsets.emplace_back(vol.indexed_vertex_array.triangle_indices.size());
}
}
for (const WipeTower::ToolChangeResult &extrusions : layer) {
@ -6148,9 +6306,9 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
assert(it_filter != filters.end() && key.first == it_filter->first);
GLVolume& vol = *it_filter->second;
vol.print_zs.push_back(layer.z);
vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
vol.print_zs.emplace_back(layer.z);
vol.offsets.emplace_back(vol.indexed_vertex_array.quad_indices.size());
vol.offsets.emplace_back(vol.indexed_vertex_array.triangle_indices.size());
_3DScene::extrusionentity_to_verts(path.polyline, path.width, path.height, layer.z, vol);
}
@ -6222,9 +6380,9 @@ inline void travel_paths_internal(
assert(it != by_type.end() && it->first == func_value(polyline));
GLVolume& vol = *it->second;
vol.print_zs.push_back(unscale<double>(polyline.polyline.bounding_box().min(2)));
vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
vol.print_zs.emplace_back(unscale<double>(polyline.polyline.bounding_box().min(2)));
vol.offsets.emplace_back(vol.indexed_vertex_array.quad_indices.size());
vol.offsets.emplace_back(vol.indexed_vertex_array.triangle_indices.size());
_3DScene::polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, vol);

View file

@ -3,6 +3,9 @@
#include <stddef.h>
#include <memory>
#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
#include <chrono>
#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
#include "3DScene.hpp"
#include "GLToolbar.hpp"
@ -389,6 +392,30 @@ private:
void render(const std::vector<const ModelInstance*>& sorted_instances) const;
};
#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI
class Tooltip
{
std::string m_text;
#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
std::chrono::steady_clock::time_point m_start_time;
#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
// Indicator that the mouse is inside an ImGUI dialog, therefore the tooltip should be suppressed.
bool m_in_imgui = false;
public:
bool is_empty() const { return m_text.empty(); }
#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
void set_text(const std::string& text);
void render(const Vec2d& mouse_position, GLCanvas3D& canvas) const;
#else
void set_text(const std::string& text) { m_text = text; }
void render(const Vec2d& mouse_position) const;
#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
// Indicates that the mouse is inside an ImGUI dialog, therefore the tooltip should be suppressed.
void set_in_imgui(bool b) { m_in_imgui = b; }
};
#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI
public:
enum ECursorType : unsigned char
{
@ -467,6 +494,9 @@ private:
int m_selected_extruder;
Labels m_labels;
#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI
mutable Tooltip m_tooltip;
#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI
public:
GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar);

View file

@ -421,14 +421,60 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
// mouse anywhere
if (!evt.Dragging() && !evt.Leaving() && !evt.Entering() && (m_mouse_capture.parent != nullptr))
{
if (m_mouse_capture.any() && (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()))
if (m_mouse_capture.any() && (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())) {
// prevents loosing selection into the scene if mouse down was done inside the toolbar and mouse up was down outside it,
// as when switching between views
processed = true;
m_mouse_capture.reset();
if (contains_mouse(mouse_pos, parent) == -1)
// mouse is outside the toolbar
m_tooltip.clear();
return true;
}
m_mouse_capture.reset();
}
#if ENABLE_MODIFIED_TOOLBAR_MOUSE_EVENT_HANDLING
if (evt.Moving())
m_tooltip = update_hover_state(mouse_pos, parent);
else if (evt.LeftUp())
{
if (m_mouse_capture.left)
{
processed = true;
m_mouse_capture.left = false;
}
else
return false;
}
else if (evt.MiddleUp())
{
if (m_mouse_capture.middle)
{
processed = true;
m_mouse_capture.middle = false;
}
else
return false;
}
else if (evt.RightUp())
{
if (m_mouse_capture.right)
{
processed = true;
m_mouse_capture.right = false;
}
else
return false;
}
else if (evt.Dragging())
{
if (m_mouse_capture.any())
// if the button down was done on this toolbar, prevent from dragging into the scene
processed = true;
else
return false;
}
#else
if (evt.Moving())
m_tooltip = update_hover_state(mouse_pos, parent);
else if (evt.LeftUp())
@ -440,6 +486,7 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
else if (evt.Dragging() && m_mouse_capture.any())
// if the button down was done on this toolbar, prevent from dragging into the scene
processed = true;
#endif // ENABLE_MODIFIED_TOOLBAR_MOUSE_EVENT_HANDLING
int item_id = contains_mouse(mouse_pos, parent);
if (item_id == -1)
@ -479,8 +526,10 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
parent.set_as_dirty();
}
}
#if !ENABLE_MODIFIED_TOOLBAR_MOUSE_EVENT_HANDLING
else if (evt.LeftUp())
processed = true;
#endif // !ENABLE_MODIFIED_TOOLBAR_MOUSE_EVENT_HANDLING
}
return processed;

View file

@ -10,6 +10,7 @@
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <boost/nowide/convert.hpp>
#include <wx/stdpaths.h>
#include <wx/imagpng.h>
@ -50,6 +51,7 @@
#ifdef __WXMSW__
#include <Shlobj.h>
#include <dbt.h>
#endif // __WXMSW__
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
@ -60,6 +62,7 @@
namespace Slic3r {
namespace GUI {
class MainFrame;
wxString file_wildcards(FileType file_type, const std::string &custom_extension)
{
@ -96,9 +99,9 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension)
static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); }
static void register_dpi_event()
{
#ifdef WIN32
static void register_win32_dpi_event()
{
enum { WM_DPICHANGED_ = 0x02e0 };
wxWindow::MSWRegisterMessageHandler(WM_DPICHANGED_, [](wxWindow *win, WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) {
@ -111,9 +114,52 @@ static void register_dpi_event()
return true;
});
#endif
}
static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 };
static void register_win32_device_notification_event()
{
enum { WM_DPICHANGED_ = 0x02e0 };
wxWindow::MSWRegisterMessageHandler(WM_DEVICECHANGE, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
// Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only.
auto main_frame = dynamic_cast<MainFrame*>(win);
auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater();
if (plater == nullptr)
// Maybe some other top level window like a dialog or maybe a pop-up menu?
return true;
PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
switch (wParam) {
case DBT_DEVICEARRIVAL:
if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME)
plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED));
else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb;
// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) {
// printf("DBT_DEVICEARRIVAL %d - Media has arrived: %ws\n", msg_count, lpdbi->dbcc_name);
if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID)
plater->GetEventHandler()->AddPendingEvent(HIDDeviceAttachedEvent(EVT_HID_DEVICE_ATTACHED, boost::nowide::narrow(lpdbi->dbcc_name)));
}
break;
case DBT_DEVICEREMOVECOMPLETE:
if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME)
plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED));
else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb;
// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME)
// printf("DBT_DEVICEARRIVAL %d - Media was removed: %ws\n", msg_count, lpdbi->dbcc_name);
if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID)
plater->GetEventHandler()->AddPendingEvent(HIDDeviceDetachedEvent(EVT_HID_DEVICE_DETACHED, boost::nowide::narrow(lpdbi->dbcc_name)));
}
break;
default:
break;
}
return true;
});
}
#endif // WIN32
static void generic_exception_handle()
{
@ -248,7 +294,10 @@ bool GUI_App::on_init_inner()
show_error(nullptr, ex.what());
}
register_dpi_event();
#ifdef WIN32
register_win32_dpi_event();
register_win32_device_notification_event();
#endif // WIN32
// Let the libslic3r know the callback, which will translate messages on demand.
Slic3r::I18N::set_translate_callback(libslic3r_translate_callback);

View file

@ -156,8 +156,11 @@ void ObjectLayers::create_layers_list()
const t_layer_height_range& range = layer.first;
auto del_btn = new PlusMinusButton(m_parent, m_bmp_delete, range);
del_btn->SetToolTip(_(L("Remove layer range")));
auto add_btn = new PlusMinusButton(m_parent, m_bmp_add, range);
add_btn->SetToolTip(_(L("Add layer range")));
wxString tooltip = wxGetApp().obj_list()->can_add_new_range_after_current(range);
add_btn->SetToolTip(tooltip.IsEmpty() ? _(L("Add layer range")) : tooltip);
add_btn->Enable(tooltip.IsEmpty());
auto sizer = create_layer(range, del_btn, add_btn);
sizer->Add(del_btn, 0, wxRIGHT | wxLEFT, em_unit(m_parent));

View file

@ -118,13 +118,20 @@ ObjectList::ObjectList(wxWindow* parent) :
// detect the current mouse position here, to pass it to list_manipulation() method
// if we detect it later, the user may have moved the mouse pointer while calculations are performed, and this would mess-up the HitTest() call performed into list_manipulation()
// see: https://github.com/prusa3d/PrusaSlicer/issues/3802
const wxPoint mouse_pos = get_mouse_position_in_control();
const wxPoint mouse_pos = this->get_mouse_position_in_control();
#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();
// wxGetApp().obj_manipul()->emulate_kill_focus(); // It's not necessury anymore #ys_FIXME delete after testing
// On Windows and Linux:
// It's not invoked KillFocus event for "temporary" panels (like "Manipulation panel", "Settings", "Layer ranges"),
// if we change selection in object list.
// see https://github.com/prusa3d/PrusaSlicer/issues/3303
// But, if we call SetFocus() for ObjectList it will cause an invoking of a KillFocus event for "temporary" panels
this->SetFocus();
#else
// To avoid selection update from SetSelection() and UnselectAll() under osx
if (m_prevent_list_events)
@ -155,7 +162,7 @@ ObjectList::ObjectList(wxWindow* parent) :
// Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected.
wxDataViewItem item;
wxDataViewColumn *col;
this->HitTest(get_mouse_position_in_control(), item, col);
this->HitTest(this->get_mouse_position_in_control(), item, col);
new_selected_column = (col == nullptr) ? -1 : (int)col->GetModelColumn();
if (new_selected_item == m_last_selected_item && m_last_selected_column != -1 && m_last_selected_column != new_selected_column) {
// Mouse clicked on another column of the active row. Simulate keyboard enter to enter the editing mode of the current column.
@ -171,7 +178,7 @@ ObjectList::ObjectList(wxWindow* parent) :
selection_changed();
#ifndef __WXMSW__
set_tooltip_for_item(get_mouse_position_in_control());
set_tooltip_for_item(this->get_mouse_position_in_control());
#endif //__WXMSW__
#ifndef __WXOSX__
@ -211,7 +218,7 @@ ObjectList::ObjectList(wxWindow* parent) :
#ifdef __WXMSW__
GetMainWindow()->Bind(wxEVT_MOTION, [this](wxMouseEvent& event) {
set_tooltip_for_item(get_mouse_position_in_control());
set_tooltip_for_item(this->get_mouse_position_in_control());
event.Skip();
});
#endif //__WXMSW__
@ -419,14 +426,6 @@ void ObjectList::set_tooltip_for_item(const wxPoint& pt)
GetMainWindow()->SetToolTip(tooltip);
}
wxPoint ObjectList::get_mouse_position_in_control()
{
const wxPoint& pt = wxGetMousePosition();
// wxWindow* win = GetMainWindow();
// wxPoint screen_pos = win->GetScreenPosition();
return wxPoint(pt.x - /*win->*/GetScreenPosition().x, pt.y - /*win->*/GetScreenPosition().y);
}
int ObjectList::get_selected_obj_idx() const
{
if (GetSelectedItemsCount() == 1)
@ -792,13 +791,7 @@ void ObjectList::OnChar(wxKeyEvent& event)
void ObjectList::OnContextMenu(wxDataViewEvent& evt)
{
// The mouse position returned by get_mouse_position_in_control() here is the one at the time the mouse button is released (mouse up event)
wxPoint mouse_pos = get_mouse_position_in_control();
// We check if the mouse down event was over the "Editing" column, if not, we change the mouse position so that the following call to list_simulation() does not show any context menu
// see: https://github.com/prusa3d/PrusaSlicer/issues/3802
wxDataViewColumn* column = evt.GetDataViewColumn();
if (column == nullptr || column->GetTitle() != _("Editing"))
mouse_pos.x = 0;
wxPoint mouse_pos = this->get_mouse_position_in_control();
// Do not show the context menu if the user pressed the right mouse button on the 3D scene and released it on the objects list
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
@ -811,6 +804,12 @@ void ObjectList::OnContextMenu(wxDataViewEvent& evt)
void ObjectList::list_manipulation(const wxPoint& mouse_pos, bool evt_context_menu/* = false*/)
{
// Interesting fact: when mouse_pos.x < 0, HitTest(mouse_pos, item, col) returns item = null, but column = last column.
// So, when mouse was moved to scene immediately after clicking in ObjectList, in the scene will be shown context menu for the Editing column.
// see: https://github.com/prusa3d/PrusaSlicer/issues/3802
if (mouse_pos.x < 0)
return;
wxDataViewItem item;
wxDataViewColumn* col = nullptr;
HitTest(mouse_pos, item, col);
@ -925,7 +924,7 @@ void ObjectList::extruder_editing()
const int column_width = GetColumn(colExtruder)->GetWidth() + wxSystemSettings::GetMetric(wxSYS_VSCROLL_X) + 5;
wxPoint pos = get_mouse_position_in_control();
wxPoint pos = this->get_mouse_position_in_control();
wxSize size = wxSize(column_width, -1);
pos.x = GetColumn(colName)->GetWidth() + GetColumn(colPrint)->GetWidth() + 5;
pos.y -= GetTextExtent("m").y;
@ -2880,13 +2879,13 @@ void ObjectList::del_layer_range(const t_layer_height_range& range)
static double get_min_layer_height(const int extruder_idx)
{
const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
return config.opt_float("min_layer_height", extruder_idx <= 0 ? 0 : extruder_idx-1);
return config.opt_float("min_layer_height", std::max(0, extruder_idx - 1));
}
static double get_max_layer_height(const int extruder_idx)
{
const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
int extruder_idx_zero_based = extruder_idx <= 0 ? 0 : extruder_idx-1;
int extruder_idx_zero_based = std::max(0, extruder_idx - 1);
double max_layer_height = config.opt_float("max_layer_height", extruder_idx_zero_based);
// In case max_layer_height is set to zero, it should default to 75 % of nozzle diameter:
@ -2896,9 +2895,11 @@ static double get_max_layer_height(const int extruder_idx)
return max_layer_height;
}
// When editing this function, please synchronize the conditions with can_add_new_range_after_current().
void ObjectList::add_layer_range_after_current(const t_layer_height_range current_range)
{
const int obj_idx = get_selected_obj_idx();
assert(obj_idx >= 0);
if (obj_idx < 0)
// This should not happen.
return;
@ -2932,12 +2933,18 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range curren
{
if (current_range.second == next_range.first)
{
// Splitting the currnet layer heigth range to two.
// Splitting the next layer height range to two.
const auto old_config = ranges.at(next_range);
const coordf_t delta = (next_range.second - next_range.first);
if (delta >= get_min_layer_height(old_config.opt_int("extruder"))/*0.05f*/) {
const coordf_t midl_layer = next_range.first + 0.5 * delta;
t_layer_height_range new_range = { midl_layer, next_range.second };
const coordf_t delta = next_range.second - next_range.first;
// Layer height of the current layer.
const coordf_t old_min_layer_height = get_min_layer_height(old_config.opt_int("extruder"));
// Layer height of the layer to be inserted.
const coordf_t new_min_layer_height = get_min_layer_height(0);
if (delta >= old_min_layer_height + new_min_layer_height - EPSILON) {
const coordf_t middle_layer_z = (new_min_layer_height > 0.5 * delta) ?
next_range.second - new_min_layer_height :
next_range.first + std::max(old_min_layer_height, 0.5 * delta);
t_layer_height_range new_range = { middle_layer_z, next_range.second };
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add Height Range")));
changed = true;
@ -2951,12 +2958,12 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range curren
ranges[new_range] = old_config;
add_layer_item(new_range, layers_item, layer_idx);
new_range = { current_range.second, midl_layer };
new_range = { current_range.second, middle_layer_z };
ranges[new_range] = get_default_layer_config(obj_idx);
add_layer_item(new_range, layers_item, layer_idx);
}
}
else
else if (next_range.first - current_range.second >= get_min_layer_height(0) - EPSILON)
{
// Filling in a gap between the current and a new layer height range with a new one.
take_snapshot(_(L("Add Height Range")));
@ -2978,6 +2985,49 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range curren
select_item(layers_item);
}
// Returning an empty string means that the layer could be added after the current layer.
// Otherwise an error tooltip is returned.
// When editing this function, please synchronize the conditions with add_layer_range_after_current().
wxString ObjectList::can_add_new_range_after_current(const t_layer_height_range current_range)
{
const int obj_idx = get_selected_obj_idx();
assert(obj_idx >= 0);
if (obj_idx < 0)
// This should not happen.
return "ObjectList assert";
t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges;
auto it_range = ranges.find(current_range);
assert(it_range != ranges.end());
if (it_range == ranges.end())
// This shoudl not happen.
return "ObjectList assert";
auto it_next_range = it_range;
if (++ it_next_range == ranges.end())
// Adding a layer after the last layer is always possible.
return "";
if (const std::pair<coordf_t, coordf_t>& next_range = it_next_range->first; current_range.second <= next_range.first)
{
if (current_range.second == next_range.first) {
if (next_range.second - next_range.first < get_min_layer_height(it_next_range->second.opt_int("extruder")) + get_min_layer_height(0) - EPSILON)
return _(L("Cannot insert a new layer range after the current layer range.\n"
"The next layer range is too thin to be split to two\n"
"without violating the minimum layer height."));
} else if (next_range.first - current_range.second < get_min_layer_height(0) - EPSILON) {
return _(L("Cannot insert a new layer range between the current and the next layer range.\n"
"The gap between the current layer range and the next layer range\n"
"is thinner than the minimum layer height allowed."));
}
} else
return _(L("Cannot insert a new layer range after the current layer range.\n"
"Current layer range overlaps with the next layer range."));
// All right, new layer height range could be inserted.
return "";
}
void ObjectList::add_layer_item(const t_layer_height_range& range,
const wxDataViewItem layers_item,
const int layer_idx /* = -1*/)
@ -2998,7 +3048,10 @@ void ObjectList::add_layer_item(const t_layer_height_range& range,
bool ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t layer_height)
{
const int obj_idx = get_selected_obj_idx();
// Use m_selected_object_id instead of get_selected_obj_idx()
// because of get_selected_obj_idx() return obj_idx for currently selected item.
// But edit_layer_range(...) function can be called, when Selection in ObjectList could be changed
const int obj_idx = m_selected_object_id ;
if (obj_idx < 0)
return false;
@ -3021,7 +3074,10 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t la
bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_layer_height_range& new_range, bool dont_update_ui)
{
const int obj_idx = get_selected_obj_idx();
// Use m_selected_object_id instead of get_selected_obj_idx()
// because of get_selected_obj_idx() return obj_idx for currently selected item.
// But edit_layer_range(...) function can be called, when Selection in ObjectList could be changed
const int obj_idx = m_selected_object_id;
if (obj_idx < 0) return false;
take_snapshot(_(L("Edit Height Range")));
@ -3048,12 +3104,13 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay
add_layer_item(r.first, root_item);
}
if (dont_update_ui)
return true;
// if this function was invoked from wxEVT_CHANGE_SELECTION selected item could be other than itLayer or itLayerRoot
if (!dont_update_ui && (sel_type & (itLayer | itLayerRoot)))
select_item(sel_type&itLayer ? m_objects_model->GetItemByLayerRange(obj_idx, new_range) : root_item);
select_item(sel_type&itLayer ? m_objects_model->GetItemByLayerRange(obj_idx, new_range) : root_item);
Expand(root_item);
m_prevent_list_events = false;
return true;
}

View file

@ -284,7 +284,7 @@ public:
bool selected_instances_of_same_object();
bool can_split_instances();
wxPoint get_mouse_position_in_control();
wxPoint get_mouse_position_in_control() const { return wxGetMousePosition() - this->GetScreenPosition(); }
wxBoxSizer* get_sizer() {return m_sizer;}
int get_selected_obj_idx() const;
DynamicPrintConfig& get_item_config(const wxDataViewItem& item) const;
@ -327,6 +327,7 @@ public:
// may have been postponed from the "kill focus" event of a text field, if the focus was lost for the "add layer" button.
// Rather providing the range by a value than by a reference, so that the memory referenced cannot be invalidated.
void add_layer_range_after_current(const t_layer_height_range current_range);
wxString can_add_new_range_after_current( t_layer_height_range current_range);
void add_layer_item (const t_layer_height_range& range,
const wxDataViewItem layers_item,
const int layer_idx = -1);

View file

@ -21,6 +21,12 @@
namespace Slic3r {
namespace GUI {
#ifdef _WIN32
wxDEFINE_EVENT(EVT_HID_DEVICE_ATTACHED, HIDDeviceAttachedEvent);
wxDEFINE_EVENT(EVT_HID_DEVICE_DETACHED, HIDDeviceDetachedEvent);
wxDEFINE_EVENT(EVT_VOLUME_ATTACHED, VolumeAttachedEvent);
wxDEFINE_EVENT(EVT_VOLUME_DETACHED, VolumeDetachedEvent);
#endif // _WIN32
wxTopLevelWindow* find_toplevel_parent(wxWindow *window)
{

View file

@ -18,6 +18,8 @@
#include <wx/debug.h>
#include <wx/settings.h>
#include "Event.hpp"
class wxCheckBox;
class wxTopLevelWindow;
class wxRect;
@ -26,6 +28,19 @@ class wxRect;
namespace Slic3r {
namespace GUI {
#ifdef _WIN32
// USB HID attach / detach events from Windows OS.
using HIDDeviceAttachedEvent = Event<std::string>;
using HIDDeviceDetachedEvent = Event<std::string>;
wxDECLARE_EVENT(EVT_HID_DEVICE_ATTACHED, HIDDeviceAttachedEvent);
wxDECLARE_EVENT(EVT_HID_DEVICE_DETACHED, HIDDeviceDetachedEvent);
// Disk aka Volume attach / detach events from Windows OS.
using VolumeAttachedEvent = SimpleEvent;
using VolumeDetachedEvent = SimpleEvent;
wxDECLARE_EVENT(EVT_VOLUME_ATTACHED, VolumeAttachedEvent);
wxDECLARE_EVENT(EVT_VOLUME_DETACHED, VolumeDetachedEvent);
#endif /* _WIN32 */
wxTopLevelWindow* find_toplevel_parent(wxWindow *window);

View file

@ -262,12 +262,6 @@ void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const
}
}
void GLGizmoBase::set_tooltip(const std::string& tooltip) const
{
m_parent.set_tooltip(tooltip);
}
std::string GLGizmoBase::format(float value, unsigned int decimals) const
{
return Slic3r::string_printf("%.*f", decimals, value);

View file

@ -100,6 +100,7 @@ protected:
mutable std::vector<Grabber> m_grabbers;
ImGuiWrapper* m_imgui;
bool m_first_input_window_render;
mutable std::string m_tooltip;
public:
GLGizmoBase(GLCanvas3D& parent,
@ -145,10 +146,12 @@ public:
void update(const UpdateData& data);
void render() const { on_render(); }
void render() const { m_tooltip.clear(); on_render(); }
void render_for_picking() const { on_render_for_picking(); }
void render_input_window(float x, float y, float bottom_limit);
virtual std::string get_tooltip() const { return ""; }
protected:
virtual bool on_init() = 0;
virtual void on_load(cereal::BinaryInputArchive& ar) {}
@ -174,7 +177,6 @@ protected:
void render_grabbers(float size) const;
void render_grabbers_for_picking(const BoundingBoxf3& box) const;
void set_tooltip(const std::string& tooltip) const;
std::string format(float value, unsigned int decimals) const;
};

View file

@ -30,6 +30,11 @@ GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, uns
, m_rotate_lower(false)
{}
std::string GLGizmoCut::get_tooltip() const
{
return (m_hover_id == 0 || m_grabbers[0].dragging) ? "Z: " + format(m_cut_z, 2) : "";
}
bool GLGizmoCut::on_init()
{
m_grabbers.emplace_back();
@ -79,10 +84,6 @@ void GLGizmoCut::on_update(const UpdateData& data)
void GLGizmoCut::on_render() const
{
if (m_grabbers[0].dragging) {
set_tooltip("Z: " + format(m_cut_z, 2));
}
const Selection& selection = m_parent.get_selection();
update_max_z(selection);

View file

@ -28,6 +28,8 @@ public:
double get_cut_z() const { return m_cut_z; }
void set_cut_z(double cut_z) const;
std::string get_tooltip() const override;
protected:
virtual bool on_init();
virtual void on_load(cereal::BinaryInputArchive& ar) { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }

View file

@ -31,6 +31,22 @@ GLGizmoMove3D::~GLGizmoMove3D()
::gluDeleteQuadric(m_quadric);
}
std::string GLGizmoMove3D::get_tooltip() const
{
const Selection& selection = m_parent.get_selection();
bool show_position = selection.is_single_full_instance();
const Vec3d& position = selection.get_bounding_box().center();
if (m_hover_id == 0 || m_grabbers[0].dragging)
return "X: " + format(show_position ? position(0) : m_displacement(0), 2);
else if (m_hover_id == 1 || m_grabbers[1].dragging)
return "Y: " + format(show_position ? position(1) : m_displacement(1), 2);
else if (m_hover_id == 2 || m_grabbers[2].dragging)
return "Z: " + format(show_position ? position(2) : m_displacement(2), 2);
else
return "";
}
bool GLGizmoMove3D::on_init()
{
for (int i = 0; i < 3; ++i)
@ -85,22 +101,6 @@ void GLGizmoMove3D::on_render() const
{
const Selection& selection = m_parent.get_selection();
bool show_position = selection.is_single_full_instance();
const Vec3d& position = selection.get_bounding_box().center();
if ((show_position && (m_hover_id == 0)) || m_grabbers[0].dragging)
set_tooltip("X: " + format(show_position ? position(0) : m_displacement(0), 2));
else if (!m_grabbers[0].dragging && (m_hover_id == 0))
set_tooltip("X");
else if ((show_position && (m_hover_id == 1)) || m_grabbers[1].dragging)
set_tooltip("Y: " + format(show_position ? position(1) : m_displacement(1), 2));
else if (!m_grabbers[1].dragging && (m_hover_id == 1))
set_tooltip("Y");
else if ((show_position && (m_hover_id == 2)) || m_grabbers[2].dragging)
set_tooltip("Z: " + format(show_position ? position(2) : m_displacement(2), 2));
else if (!m_grabbers[2].dragging && (m_hover_id == 2))
set_tooltip("Z");
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));

View file

@ -30,6 +30,8 @@ public:
const Vec3d& get_displacement() const { return m_displacement; }
std::string get_tooltip() const override;
protected:
virtual bool on_init();
virtual std::string on_get_name() const;

View file

@ -67,6 +67,18 @@ void GLGizmoRotate::set_angle(double angle)
m_angle = angle;
}
std::string GLGizmoRotate::get_tooltip() const
{
std::string axis;
switch (m_axis)
{
case X: { axis = "X"; break; }
case Y: { axis = "Y"; break; }
case Z: { axis = "Z"; break; }
}
return (m_hover_id == 0 || m_grabbers[0].dragging) ? axis + ": " + format((float)Geometry::rad2deg(m_angle), 4) : "";
}
bool GLGizmoRotate::on_init()
{
m_grabbers.push_back(Grabber());
@ -127,19 +139,7 @@ void GLGizmoRotate::on_render() const
const Selection& selection = m_parent.get_selection();
const BoundingBoxf3& box = selection.get_bounding_box();
std::string axis;
switch (m_axis)
{
case X: { axis = "X"; break; }
case Y: { axis = "Y"; break; }
case Z: { axis = "Z"; break; }
}
if (!m_dragging && (m_hover_id == 0))
set_tooltip(axis);
else if (m_dragging)
set_tooltip(axis + ": " + format((float)Geometry::rad2deg(m_angle), 4) + "\u00B0");
else
if (m_hover_id != 0 && !m_grabbers[0].dragging)
{
m_center = box.center();
m_radius = Offset + box.radius();

View file

@ -49,6 +49,8 @@ public:
double get_angle() const { return m_angle; }
void set_angle(double angle);
std::string get_tooltip() const override;
protected:
virtual bool on_init();
virtual std::string on_get_name() const { return ""; }
@ -81,6 +83,16 @@ public:
Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); }
void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); }
std::string get_tooltip() const override
{
std::string tooltip = m_gizmos[X].get_tooltip();
if (tooltip.empty())
tooltip = m_gizmos[Y].get_tooltip();
if (tooltip.empty())
tooltip = m_gizmos[Z].get_tooltip();
return tooltip;
}
protected:
virtual bool on_init();
virtual std::string on_get_name() const;

View file

@ -20,6 +20,38 @@ GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filen
{
}
std::string GLGizmoScale3D::get_tooltip() const
{
const Selection& selection = m_parent.get_selection();
bool single_instance = selection.is_single_full_instance();
bool single_volume = selection.is_single_modifier() || selection.is_single_volume();
bool single_selection = single_instance || single_volume;
Vec3f scale = 100.0f * Vec3f::Ones();
if (single_instance)
scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast<float>();
else if (single_volume)
scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast<float>();
if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging)
return "X: " + format(scale(0), 4) + "%";
else if (m_hover_id == 2 || m_hover_id == 3 || m_grabbers[2].dragging || m_grabbers[3].dragging)
return "Y: " + format(scale(1), 4) + "%";
else if (m_hover_id == 4 || m_hover_id == 5 || m_grabbers[4].dragging || m_grabbers[5].dragging)
return "Z: " + format(scale(2), 4) + "%";
else if (m_hover_id == 6 || m_hover_id == 7 || m_hover_id == 8 || m_hover_id == 9 ||
m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging)
{
std::string tooltip = "X: " + format(scale(0), 4) + "%\n";
tooltip += "Y: " + format(scale(1), 4) + "%\n";
tooltip += "Z: " + format(scale(2), 4) + "%";
return tooltip;
}
else
return "";
}
bool GLGizmoScale3D::on_init()
{
for (int i = 0; i < 10; ++i)
@ -89,37 +121,6 @@ void GLGizmoScale3D::on_render() const
bool single_instance = selection.is_single_full_instance();
bool single_volume = selection.is_single_modifier() || selection.is_single_volume();
bool single_selection = single_instance || single_volume;
Vec3f scale = 100.0f * Vec3f::Ones();
if (single_instance)
scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast<float>();
else if (single_volume)
scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast<float>();
if ((single_selection && ((m_hover_id == 0) || (m_hover_id == 1))) || m_grabbers[0].dragging || m_grabbers[1].dragging)
set_tooltip("X: " + format(scale(0), 4) + "%");
else if (!m_grabbers[0].dragging && !m_grabbers[1].dragging && ((m_hover_id == 0) || (m_hover_id == 1)))
set_tooltip("X");
else if ((single_selection && ((m_hover_id == 2) || (m_hover_id == 3))) || m_grabbers[2].dragging || m_grabbers[3].dragging)
set_tooltip("Y: " + format(scale(1), 4) + "%");
else if (!m_grabbers[2].dragging && !m_grabbers[3].dragging && ((m_hover_id == 2) || (m_hover_id == 3)))
set_tooltip("Y");
else if ((single_selection && ((m_hover_id == 4) || (m_hover_id == 5))) || m_grabbers[4].dragging || m_grabbers[5].dragging)
set_tooltip("Z: " + format(scale(2), 4) + "%");
else if (!m_grabbers[4].dragging && !m_grabbers[5].dragging && ((m_hover_id == 4) || (m_hover_id == 5)))
set_tooltip("Z");
else if ((single_selection && ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9)))
|| m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging)
{
std::string tooltip = "X: " + format(scale(0), 4) + "%\n";
tooltip += "Y: " + format(scale(1), 4) + "%\n";
tooltip += "Z: " + format(scale(2), 4) + "%";
set_tooltip(tooltip);
}
else if (!m_grabbers[6].dragging && !m_grabbers[7].dragging && !m_grabbers[8].dragging && !m_grabbers[9].dragging &&
((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9)))
set_tooltip("X/Y/Z");
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));

View file

@ -42,6 +42,8 @@ public:
const Vec3d& get_offset() const { return m_offset; }
std::string get_tooltip() const override;
protected:
virtual bool on_init();
virtual std::string on_get_name() const;

View file

@ -225,7 +225,7 @@ void GLGizmosManager::update_data()
set_scale(Vec3d::Ones());
set_rotation(Vec3d::Zero());
set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
set_sla_support_data(nullptr);
set_sla_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
}
}
@ -422,6 +422,15 @@ void GLGizmosManager::render_overlay() const
do_render_overlay();
}
std::string GLGizmosManager::get_tooltip() const
{
if (!m_tooltip.empty())
return m_tooltip;
const GLGizmoBase* curr = get_current();
return (curr != nullptr) ? curr->get_tooltip() : "";
}
bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt)
{
bool processed = false;
@ -447,6 +456,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
int selected_object_idx = selection.get_object_idx();
bool processed = false;
#if !ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING
// mouse anywhere
if (!evt.Dragging() && !evt.Leaving() && !evt.Entering() && (m_mouse_capture.parent != nullptr))
{
@ -456,10 +466,81 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
m_mouse_capture.reset();
}
#endif // !ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING
// mouse anywhere
if (evt.Moving())
m_tooltip = update_hover_state(mouse_pos);
#if ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING
else if (evt.LeftUp())
{
if (m_mouse_capture.left)
{
processed = true;
m_mouse_capture.left = false;
}
else if (is_dragging())
{
switch (m_current) {
case Move: m_parent.do_move(L("Gizmo-Move")); break;
case Scale: m_parent.do_scale(L("Gizmo-Scale")); break;
case Rotate: m_parent.do_rotate(L("Gizmo-Rotate")); break;
default: break;
}
stop_dragging();
update_data();
wxGetApp().obj_manipul()->set_dirty();
// Let the plater know that the dragging finished, so a delayed refresh
// of the scene with the background processing data should be performed.
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
// updates camera target constraints
m_parent.refresh_camera_scene_box();
processed = true;
}
// else
// return false;
}
else if (evt.MiddleUp())
{
if (m_mouse_capture.middle)
{
processed = true;
m_mouse_capture.middle = false;
}
else
return false;
}
else if (evt.RightUp())
{
if (pending_right_up)
{
pending_right_up = false;
return true;
}
if (m_mouse_capture.right)
{
processed = true;
m_mouse_capture.right = false;
}
else
return false;
}
#if ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX
else if (evt.Dragging() && !is_dragging())
#else
else if (evt.Dragging()))
#endif // ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX
{
if (m_mouse_capture.any())
// if the button down was done on this toolbar, prevent from dragging into the scene
processed = true;
// else
// return false;
}
#else
else if (evt.LeftUp())
m_mouse_capture.left = false;
else if (evt.MiddleUp())
@ -476,6 +557,55 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
else if (evt.Dragging() && m_mouse_capture.any())
// if the button down was done on this toolbar, prevent from dragging into the scene
processed = true;
#endif // ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING
#if ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX
else if (evt.Dragging() && is_dragging())
{
if (!m_parent.get_wxglcanvas()->HasCapture())
m_parent.get_wxglcanvas()->CaptureMouse();
m_parent.set_mouse_as_dragging();
update(m_parent.mouse_ray(pos), pos);
switch (m_current)
{
case Move:
{
// Apply new temporary offset
selection.translate(get_displacement());
wxGetApp().obj_manipul()->set_dirty();
break;
}
case Scale:
{
// Apply new temporary scale factors
TransformationType transformation_type(TransformationType::Local_Absolute_Joint);
if (evt.AltDown())
transformation_type.set_independent();
selection.scale(get_scale(), transformation_type);
if (evt.ControlDown())
selection.translate(get_scale_offset(), true);
wxGetApp().obj_manipul()->set_dirty();
break;
}
case Rotate:
{
// Apply new temporary rotations
TransformationType transformation_type(TransformationType::World_Relative_Joint);
if (evt.AltDown())
transformation_type.set_independent();
selection.rotate(get_rotation(), transformation_type);
wxGetApp().obj_manipul()->set_dirty();
break;
}
default:
break;
}
m_parent.set_as_dirty();
processed = true;
}
#endif // ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX
if (get_gizmo_idx_from_mouse(mouse_pos) == Undefined)
{
@ -518,6 +648,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
m_parent.set_as_dirty();
processed = true;
}
#if !ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX
else if (evt.Dragging() && is_dragging())
{
if (!m_parent.get_wxglcanvas()->HasCapture())
@ -564,6 +695,8 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
m_parent.set_as_dirty();
processed = true;
}
#endif // !ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX
#if !ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING
else if (evt.LeftUp() && is_dragging())
{
switch (m_current) {
@ -585,6 +718,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
processed = true;
}
#endif // !ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING
else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow) && !m_parent.is_mouse_dragging())
{
// in case SLA gizmo is selected, we just pass the LeftUp event and stop processing - neither
@ -623,8 +757,10 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
m_mouse_capture.right = true;
m_mouse_capture.parent = &m_parent;
}
#if !ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING
else if (evt.LeftUp())
processed = true;
#endif // !ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING
}
return processed;

View file

@ -204,7 +204,7 @@ public:
void render_overlay() const;
const std::string& get_tooltip() const { return m_tooltip; }
std::string get_tooltip() const;
bool on_mouse(wxMouseEvent& evt);
bool on_mouse_wheel(wxMouseEvent& evt);

View file

@ -1,10 +1,12 @@
#ifndef _
#define _(s) Slic3r::GUI::I18N::translate((s))
#define _(s) Slic3r::GUI::I18N::translate((s))
#define _L(s) Slic3r::GUI::I18N::translate((s))
#define _utf8(s) Slic3r::GUI::I18N::translate_utf8((s))
#define _u8L(s) Slic3r::GUI::I18N::translate_utf8((s))
#endif /* _ */
#ifndef _CTX
#define _CTX(s, ctx) Slic3r::GUI::I18N::translate((s), (ctx))
#define _CTX(s, ctx) Slic3r::GUI::I18N::translate((s), (ctx))
#define _CTX_utf8(s, ctx) Slic3r::GUI::I18N::translate_utf8((s), (ctx))
#endif /* _ */

View file

@ -441,15 +441,37 @@ bool ImGuiWrapper::want_any_input() const
return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput;
}
#ifdef __APPLE__
static const ImWchar ranges_keyboard_shortcuts[] =
{
0x21E7, 0x21E7, // OSX Shift Key symbol
0x2318, 0x2318, // OSX Command Key symbol
0x2325, 0x2325, // OSX Option Key symbol
0,
};
#endif // __APPLE__
void ImGuiWrapper::init_font(bool compress)
{
destroy_font();
ImGuiIO& io = ImGui::GetIO();
io.Fonts->Clear();
//FIXME replace with io.Fonts->AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, m_font_size, nullptr, m_glyph_ranges);
// Create ranges of characters from m_glyph_ranges, possibly adding some OS specific special characters.
ImVector<ImWchar> ranges;
ImFontAtlas::GlyphRangesBuilder builder;
builder.AddRanges(m_glyph_ranges);
#ifdef __APPLE__
if (m_font_cjk)
// Apple keyboard shortcuts are only contained in the CJK fonts.
builder.AddRanges(ranges_keyboard_shortcuts);
#endif
builder.BuildRanges(&ranges); // Build the final result (ordered ranges with all the unique characters submitted)
//FIXME replace with io.Fonts->AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, m_font_size, nullptr, ranges.Data);
//https://github.com/ocornut/imgui/issues/220
ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + (m_font_cjk ? "NotoSansCJK-Regular.ttc" : "NotoSans-Regular.ttf")).c_str(), m_font_size, nullptr, m_glyph_ranges);
ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + (m_font_cjk ? "NotoSansCJK-Regular.ttc" : "NotoSans-Regular.ttf")).c_str(), m_font_size, nullptr, ranges.Data);
if (font == nullptr) {
font = io.Fonts->AddFontDefault();
if (font == nullptr) {
@ -457,6 +479,16 @@ void ImGuiWrapper::init_font(bool compress)
}
}
#ifdef __APPLE__
ImFontConfig config;
config.MergeMode = true;
if (! m_font_cjk) {
// Apple keyboard shortcuts are only contained in the CJK fonts.
ImFont *font_cjk = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/NotoSansCJK-Regular.ttc").c_str(), m_font_size, &config, ranges_keyboard_shortcuts);
assert(font_cjk != nullptr);
}
#endif
// Build texture atlas
unsigned char* pixels;
int width, height;

View file

@ -144,9 +144,7 @@ void KBShortcutsDialog::fill_shortcuts()
{ ctrl + "J", L("Print host upload queue") },
// View
{ "0-6", L("Camera view") },
#if ENABLE_SHOW_SCENE_LABELS
{ "E", L("Show/Hide object/instance labels") },
#endif // ENABLE_SHOW_SCENE_LABELS
// Configuration
{ ctrl + "P", L("Preferences") },
// Help

View file

@ -31,6 +31,10 @@
#include <fstream>
#include "GUI_App.hpp"
#ifdef _WIN32
#include <dbt.h>
#endif // _WIN32
namespace Slic3r {
namespace GUI {
@ -104,6 +108,31 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
update_title();
// declare events
Bind(wxEVT_CREATE, [this](wxWindowCreateEvent& event) {
#ifdef _WIN32
//static GUID GUID_DEVINTERFACE_USB_DEVICE = { 0xA5DCBF10, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED };
//static GUID GUID_DEVINTERFACE_DISK = { 0x53f56307, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b };
//static GUID GUID_DEVINTERFACE_VOLUME = { 0x71a27cdd, 0x812a, 0x11d0, 0xbe, 0xc7, 0x08, 0x00, 0x2b, 0xe2, 0x09, 0x2f };
static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 };
// Register USB HID (Human Interface Devices) notifications to trigger the 3DConnexion enumeration.
DEV_BROADCAST_DEVICEINTERFACE NotificationFilter = { 0 };
NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
NotificationFilter.dbcc_classguid = GUID_DEVINTERFACE_HID;
m_hDeviceNotify = ::RegisterDeviceNotification(this->GetHWND(), &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
// or register for file handle change?
// DEV_BROADCAST_HANDLE NotificationFilter = { 0 };
// NotificationFilter.dbch_size = sizeof(DEV_BROADCAST_HANDLE);
// NotificationFilter.dbch_devicetype = DBT_DEVTYP_HANDLE;
#endif // _WIN32
// propagate event
event.Skip();
});
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) {
if (event.CanVeto() && !wxGetApp().check_unsaved_changes()) {
event.Veto();
@ -131,6 +160,11 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
// Called when closing the application and when switching the application language.
void MainFrame::shutdown()
{
#ifdef _WIN32
::UnregisterDeviceNotification(HDEVNOTIFY(m_hDeviceNotify));
m_hDeviceNotify = nullptr;
#endif // _WIN32
if (m_plater)
m_plater->stop_jobs();

View file

@ -141,6 +141,10 @@ public:
wxNotebook* m_tabpanel { nullptr };
wxProgressDialog* m_progress_dialog { nullptr };
std::shared_ptr<ProgressStatusBar> m_statusbar;
#ifdef _WIN32
void* m_hDeviceNotify { nullptr };
#endif // _WIN32
};
} // GUI

View file

@ -99,6 +99,25 @@ void Mouse3DController::State::append_button(unsigned int id, size_t /* input_qu
}
#ifdef WIN32
// Called by Win32 HID enumeration callback.
void Mouse3DController::device_attached(const std::string &device)
{
int vid = 0;
int pid = 0;
if (sscanf(device.c_str(), "\\\\?\\HID#VID_%x&PID_%x&", &vid, &pid) == 2) {
// BOOST_LOG_TRIVIAL(trace) << boost::format("Mouse3DController::device_attached(VID_%04xxPID_%04x)") % vid % pid;
// BOOST_LOG_TRIVIAL(trace) << "Mouse3DController::device_attached: " << device;
if (std::find(_3DCONNEXION_VENDORS.begin(), _3DCONNEXION_VENDORS.end(), vid) != _3DCONNEXION_VENDORS.end()) {
// Signal the worker thread to wake up and enumerate HID devices, if not connected at the moment.
// The message may come multiple times per each USB device. For example, some USB wireless dongles register as multiple HID sockets
// for multiple devices to connect to.
// Never mind, enumeration will be performed until connected.
m_wakeup = true;
m_stop_condition.notify_all();
}
}
}
// Filter out mouse scroll events produced by the 3DConnexion driver.
bool Mouse3DController::State::process_mouse_wheel()
{
@ -388,9 +407,13 @@ void Mouse3DController::disconnected()
m_params_by_device[m_device_str] = m_params_ui;
m_device_str.clear();
m_connected = false;
wxGetApp().plater()->get_camera().recover_from_free_camera();
wxGetApp().plater()->set_current_canvas_as_dirty();
wxWakeUpIdle();
wxGetApp().plater()->CallAfter([]() {
Plater *plater = wxGetApp().plater();
if (plater != nullptr) {
plater->get_camera().recover_from_free_camera();
plater->set_current_canvas_as_dirty();
}
});
}
}
@ -486,6 +509,11 @@ void Mouse3DController::run()
return;
}
#ifdef _WIN32
// Enumerate once just after thread start.
m_wakeup = true;
#endif // _WIN32
for (;;) {
{
tbb::mutex::scoped_lock lock(m_params_ui_mutex);
@ -518,7 +546,13 @@ bool Mouse3DController::connect_device()
{
// Wait for 2 seconds, but cancellable by m_stop.
std::unique_lock<std::mutex> lock(m_stop_condition_mutex);
m_stop_condition.wait_for(lock, std::chrono::seconds(2), [this]{ return this->m_stop; });
#ifdef _WIN32
// Wait indifinetely for the stop signal.
m_stop_condition.wait(lock, [this]{ return m_stop || m_wakeup; });
m_wakeup = false;
#else
m_stop_condition.wait_for(lock, std::chrono::seconds(2), [this]{ return m_stop; });
#endif
}
if (m_stop)
@ -528,10 +562,14 @@ bool Mouse3DController::connect_device()
hid_device_info* devices = hid_enumerate(0, 0);
if (devices == nullptr)
{
BOOST_LOG_TRIVIAL(error) << "Unable to enumerate HID devices";
BOOST_LOG_TRIVIAL(trace) << "Mouse3DController::connect_device() - no HID device enumerated.";
return false;
}
#ifdef _WIN32
BOOST_LOG_TRIVIAL(trace) << "Mouse3DController::connect_device() - enumerating HID devices.";
#endif // _WIN32
// Searches for 1st connected 3Dconnexion device
struct DeviceData
{
@ -785,9 +823,17 @@ void Mouse3DController::disconnect_device()
}
m_device_str.clear();
m_connected = false;
wxGetApp().plater()->get_camera().recover_from_free_camera();
wxGetApp().plater()->set_current_canvas_as_dirty();
wxWakeUpIdle();
#ifdef _WIN32
// Enumerate once immediately after disconnect.
m_wakeup = true;
#endif // _WIN32
wxGetApp().plater()->CallAfter([]() {
Plater *plater = wxGetApp().plater();
if (plater != nullptr) {
plater->get_camera().recover_from_free_camera();
plater->set_current_canvas_as_dirty();
}
});
}
}

View file

@ -148,6 +148,9 @@ class Mouse3DController
hid_device* m_device { nullptr };
// Using m_stop_condition_mutex to synchronize m_stop.
bool m_stop { false };
#ifdef _WIN32
std::atomic<bool> m_wakeup { false };
#endif /* _WIN32 */
// Mutex and condition variable for sleeping during the detection of 3DConnexion devices by polling while allowing
// cancellation before the end of the polling interval.
std::mutex m_stop_condition_mutex;
@ -185,6 +188,9 @@ public:
#endif // __APPLE__
#ifdef WIN32
// Called by Win32 HID enumeration callback.
void device_attached(const std::string &device);
// On Windows, the 3DConnexion driver sends out mouse wheel rotation events to an active application
// if the application does not register at the driver. This is a workaround to ignore these superfluous
// mouse wheel events.

View file

@ -2016,6 +2016,7 @@ struct Plater::priv
mutable bool ready_to_slice = { false };
// Flag indicating that the G-code export targets a removable device, therefore the show_action_buttons() needs to be called at any case when the background processing finishes.
bool writing_to_removable_device = { false };
bool inside_snapshot_capture() { return m_prevent_snapshots != 0; }
private:
bool init_object_menu();
@ -2212,10 +2213,16 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
// Load the 3DConnexion device database.
mouse3d_controller.load_config(*wxGetApp().app_config);
// Start the background thread to detect and connect to a HID device (Windows and Linux).
// Connect to a 3DConnextion driver (OSX).
// Connect to a 3DConnextion driver (OSX).
mouse3d_controller.init();
#ifdef _WIN32
// Register an USB HID (Human Interface Device) attach event. evt contains Win32 path to the USB device containing VID, PID and other info.
// This event wakes up the Mouse3DController's background thread to enumerate HID devices, if the VID of the callback event
// is one of the 3D Mouse vendors (3DConnexion or Logitech).
this->q->Bind(EVT_HID_DEVICE_ATTACHED, [this](HIDDeviceAttachedEvent &evt) {
mouse3d_controller.device_attached(evt.data);
});
#endif /* _WIN32 */
this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this](RemovableDriveEjectEvent &evt) {
if (evt.data.second) {
@ -2229,6 +2236,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this](RemovableDrivesChangedEvent &) { this->show_action_buttons(this->ready_to_slice); });
// Start the background thread and register this window as a target for update events.
wxGetApp().removable_drive_manager()->init(this->q);
#ifdef _WIN32
// Trigger enumeration of removable media on Win32 notification.
this->q->Bind(EVT_VOLUME_ATTACHED, [this](VolumeAttachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); });
this->q->Bind(EVT_VOLUME_DETACHED, [this](VolumeDetachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); });
#endif /* _WIN32 */
// Initialize the Undo / Redo stack with a first snapshot.
this->take_snapshot(_(L("New Project")));
@ -2298,13 +2310,6 @@ void Plater::priv::reset_all_gizmos()
// Update the UI based on the current preferences.
void Plater::priv::update_ui_from_settings()
{
// TODO: (?)
// my ($self) = @_;
// if (defined($self->{btn_reslice}) && $self->{buttons_sizer}->IsShown($self->{btn_reslice}) != (! wxTheApp->{app_config}->get("background_processing"))) {
// $self->{buttons_sizer}->Show($self->{btn_reslice}, ! wxTheApp->{app_config}->get("background_processing"));
// $self->{buttons_sizer}->Layout;
// }
camera.set_type(wxGetApp().app_config->get("use_perspective_camera"));
if (wxGetApp().app_config->get("use_free_camera") != "1")
camera.recover_from_free_camera();
@ -2521,7 +2526,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
selection.add_object((unsigned int)idx, false);
}
if (view3D->get_canvas3d()->get_gizmos_manager().is_running())
if (view3D->get_canvas3d()->get_gizmos_manager().is_enabled())
// this is required because the selected object changed and the flatten on face an sla support gizmos need to be updated accordingly
view3D->get_canvas3d()->update_gizmos_on_off_state();
}
@ -3655,8 +3660,8 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
//! instead of
//! combo->GetStringSelection().ToUTF8().data());
const std::string& selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data();
const std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, selected_string);
const std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type,
Preset::remove_suffix_modified(combo->GetString(combo->GetSelection()).ToUTF8().data()));
if (preset_type == Preset::TYPE_FILAMENT) {
wxGetApp().preset_bundle->set_filament_preset(idx, preset_name);
@ -5761,6 +5766,7 @@ bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); }
const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); }
void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); }
void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); }
bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); }
// Wrapper around wxWindow::PopupMenu to suppress error messages popping out while tracking the popup menu.
bool Plater::PopupMenu(wxMenu *menu, const wxPoint& pos)

View file

@ -320,6 +320,8 @@ public:
Plater *m_plater;
};
bool inside_snapshot_capture();
// Wrapper around wxWindow::PopupMenu to suppress error messages popping out while tracking the popup menu.
bool PopupMenu(wxMenu *menu, const wxPoint& pos = wxDefaultPosition);
bool PopupMenu(wxMenu *menu, int x, int y) { return this->PopupMenu(menu, wxPoint(x, y)); }

View file

@ -381,7 +381,7 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config)
return;
is_visible = app_config.get_variant(vendor->id, model, variant);
} else if (type == TYPE_FILAMENT || type == TYPE_SLA_MATERIAL) {
const char *section_name = (type == TYPE_FILAMENT) ? "filaments" : "sla_materials";
const std::string &section_name = (type == TYPE_FILAMENT) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
if (app_config.has_section(section_name)) {
// Check whether this profile is marked as "installed" in PrusaSlicer.ini,
// or whether a profile is marked as "installed", which this profile may have been renamed from.
@ -413,7 +413,7 @@ const std::vector<std::string>& Preset::print_options()
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed",
"top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed",
"bridge_speed", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration",
"bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height",
"bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield",
"min_skirt_length", "brim_width", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
"raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing",
"support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers",
@ -1032,6 +1032,14 @@ const std::string& PresetCollection::get_preset_name_by_alias(const std::string&
return alias;
}
const std::string* PresetCollection::get_preset_name_renamed(const std::string &old_name) const
{
auto it_renamed = m_map_system_profile_renamed.find(old_name);
if (it_renamed != m_map_system_profile_renamed.end())
return &it_renamed->second;
return nullptr;
}
const std::string& PresetCollection::get_suffix_modified() {
return g_suffix_modified;
}

View file

@ -237,6 +237,7 @@ public:
static void update_suffix_modified();
static const std::string& suffix_modified();
static std::string remove_suffix_modified(const std::string& name);
static void normalize(DynamicPrintConfig &config);
// Report configuration fields, which are misplaced into a wrong group, remove them from the config.
static std::string remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config);
@ -244,7 +245,6 @@ public:
protected:
friend class PresetCollection;
friend class PresetBundle;
static std::string remove_suffix_modified(const std::string &name);
};
bool is_compatible_with_print (const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print, const PresetWithVendorProfile &active_printer);
@ -361,7 +361,8 @@ public:
PresetWithVendorProfile get_preset_with_vendor_profile(const Preset &preset) const;
PresetWithVendorProfile get_edited_preset_with_vendor_profile() const { return this->get_preset_with_vendor_profile(this->get_edited_preset()); }
const std::string& get_preset_name_by_alias(const std::string& alias) const;
const std::string& get_preset_name_by_alias(const std::string& alias) const;
const std::string* get_preset_name_renamed(const std::string &old_name) const;
// used to update preset_choice from Tab
const std::deque<Preset>& get_presets() const { return m_presets; }

View file

@ -289,17 +289,7 @@ std::string PresetBundle::load_system_presets()
this->reset(false);
}
this->prints .update_map_system_profile_renamed();
this->sla_prints .update_map_system_profile_renamed();
this->filaments .update_map_system_profile_renamed();
this->sla_materials.update_map_system_profile_renamed();
this->printers .update_map_system_profile_renamed();
this->prints .update_map_alias_to_profile_name();
this->sla_prints .update_map_alias_to_profile_name();
this->filaments .update_map_alias_to_profile_name();
this->sla_materials.update_map_alias_to_profile_name();
this->update_system_maps();
return errors_cummulative;
}
@ -324,6 +314,20 @@ std::vector<std::string> PresetBundle::merge_presets(PresetBundle &&other)
return duplicate_prints;
}
void PresetBundle::update_system_maps()
{
this->prints .update_map_system_profile_renamed();
this->sla_prints .update_map_system_profile_renamed();
this->filaments .update_map_system_profile_renamed();
this->sla_materials.update_map_system_profile_renamed();
this->printers .update_map_system_profile_renamed();
this->prints .update_map_alias_to_profile_name();
this->sla_prints .update_map_alias_to_profile_name();
this->filaments .update_map_alias_to_profile_name();
this->sla_materials.update_map_alias_to_profile_name();
}
static inline std::string remove_ini_suffix(const std::string &name)
{
std::string out = name;
@ -337,9 +341,9 @@ static inline std::string remove_ini_suffix(const std::string &name)
// If the "vendor" section is missing, enable all models and variants of the particular vendor.
void PresetBundle::load_installed_printers(const AppConfig &config)
{
for (auto &preset : printers) {
this->update_system_maps();
for (auto &preset : printers)
preset.set_visible_from_appconfig(config);
}
}
const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& preset_type, const std::string& alias) const
@ -367,7 +371,7 @@ void PresetBundle::load_installed_filaments(AppConfig &config)
if (printer.is_visible && printer.printer_technology() == ptFFF) {
const PresetWithVendorProfile printer_with_vendor_profile = printers.get_preset_with_vendor_profile(printer);
for (const Preset &filament : filaments)
if (is_compatible_with_printer(filaments.get_preset_with_vendor_profile(filament), printer_with_vendor_profile))
if (filament.is_system && is_compatible_with_printer(filaments.get_preset_with_vendor_profile(filament), printer_with_vendor_profile))
compatible_filaments.insert(&filament);
}
// and mark these filaments as installed, therefore this code will not be executed at the next start of the application.
@ -390,7 +394,7 @@ void PresetBundle::load_installed_sla_materials(AppConfig &config)
if (printer.is_visible && printer.printer_technology() == ptSLA) {
const PresetWithVendorProfile printer_with_vendor_profile = printers.get_preset_with_vendor_profile(printer);
for (const Preset &material : sla_materials)
if (is_compatible_with_printer(sla_materials.get_preset_with_vendor_profile(material), printer_with_vendor_profile))
if (material.is_system && is_compatible_with_printer(sla_materials.get_preset_with_vendor_profile(material), printer_with_vendor_profile))
comp_sla_materials.insert(&material);
}
// and mark these SLA materials as installed, therefore this code will not be executed at the next start of the application.
@ -1069,7 +1073,11 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree, co
// Iterate in a reverse order, so the last change will be placed first in merged.
for (auto it_inherits = prst->inherits.rbegin(); it_inherits != prst->inherits.rend(); ++ it_inherits)
for (auto it = (*it_inherits)->node->begin(); it != (*it_inherits)->node->end(); ++ it)
if (prst->node->find(it->first) == prst->node->not_found())
if (it->first == "renamed_from") {
// Don't inherit "renamed_from" flag, it does not make sense. The "renamed_from" flag only makes sense for a concrete preset.
if (boost::starts_with((*it_inherits)->name, "*"))
BOOST_LOG_TRIVIAL(error) << boost::format("Nonpublic intermediate preset %1% contains a \"renamed_from\" field, which is ignored") % (*it_inherits)->name;
} else if (prst->node->find(it->first) == prst->node->not_found())
prst->node->add_child(it->first, it->second);
}

View file

@ -42,6 +42,8 @@ public:
PresetCollection sla_prints;
PresetCollection filaments;
PresetCollection sla_materials;
PresetCollection& materials(PrinterTechnology pt) { return pt == ptFFF ? this->filaments : this->sla_materials; }
const PresetCollection& materials(PrinterTechnology pt) const { return pt == ptFFF ? this->filaments : this->sla_materials; }
PrinterPresetCollection printers;
// Filament preset names for a multi-extruder or multi-material print.
// extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size()
@ -144,6 +146,8 @@ private:
std::string load_system_presets();
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> merge_presets(PresetBundle &&other);
// Update renamed_from and alias maps of system profiles.
void update_system_maps();
// Set the is_visible flag for filaments and sla materials,
// apply defaults based on enabled printers when no filaments/materials are installed.

View file

@ -56,7 +56,7 @@ std::vector<DriveData> RemovableDriveManager::search_for_removable_drives() cons
volume_name.erase(volume_name.begin() + wcslen(volume_name.c_str()), volume_name.end());
if (! file_system_name.empty()) {
ULARGE_INTEGER free_space;
::GetDiskFreeSpaceExA(path.c_str(), &free_space, nullptr, nullptr);
::GetDiskFreeSpaceExW(wpath.c_str(), &free_space, nullptr, nullptr);
if (free_space.QuadPart > 0) {
path += "\\";
current_drives.emplace_back(DriveData{ boost::nowide::narrow(volume_name), path });
@ -86,7 +86,7 @@ void RemovableDriveManager::eject_drive()
// get handle to device
std::string mpath = "\\\\.\\" + m_last_save_path;
mpath = mpath.substr(0, mpath.size() - 1);
HANDLE handle = CreateFileA(mpath.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
HANDLE handle = CreateFileW(boost::nowide::widen(mpath).c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
if (handle == INVALID_HANDLE_VALUE) {
std::cerr << "Ejecting " << mpath << " failed " << GetLastError() << " \n";
assert(m_callback_evt_handler);
@ -128,7 +128,7 @@ std::string RemovableDriveManager::get_removable_drive_path(const std::string &p
return std::string();
std::size_t found = path.find_last_of("\\");
std::string new_path = path.substr(0, found);
int letter = PathGetDriveNumberA(new_path.c_str());
int letter = PathGetDriveNumberW(boost::nowide::widen(new_path).c_str());
for (const DriveData &drive_data : m_current_drives) {
char drive = drive_data.path[0];
if (drive == 'A' + letter)
@ -142,7 +142,7 @@ std::string RemovableDriveManager::get_removable_drive_from_path(const std::stri
tbb::mutex::scoped_lock lock(m_drives_mutex);
std::size_t found = path.find_last_of("\\");
std::string new_path = path.substr(0, found);
int letter = PathGetDriveNumberA(new_path.c_str());
int letter = PathGetDriveNumberW(boost::nowide::widen(new_path).c_str());
for (const DriveData &drive_data : m_current_drives) {
assert(! drive_data.path.empty());
if (drive_data.path.front() == 'A' + letter)
@ -151,93 +151,16 @@ std::string RemovableDriveManager::get_removable_drive_from_path(const std::stri
return std::string();
}
#if 0
// currently not used, left for possible future use
INT_PTR WINAPI WinProcCallback(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
// Called by Win32 Volume arrived / detached callback.
void RemovableDriveManager::volumes_changed()
{
// here we need to catch messeges about device removal
// problem is that when ejecting usb (how is it implemented above) there is no messege dispached. Only after physical removal of the device.
//uncomment register_window() in init() to register and comment update() in GUI_App.cpp (only for windows!) to stop recieving periodical updates
LRESULT lRet = 1;
static HDEVNOTIFY hDeviceNotify;
static constexpr GUID WceusbshGUID = { 0x25dbce51, 0x6c8f, 0x4a72, 0x8a,0x6d,0xb5,0x4c,0x2b,0x4f,0xc8,0x35 };
switch (message)
{
case WM_CREATE:
DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
NotificationFilter.dbcc_classguid = WceusbshGUID;
hDeviceNotify = RegisterDeviceNotification(hWnd, &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
break;
case WM_DEVICECHANGE:
{
// here is the important
if(wParam == DBT_DEVICEREMOVECOMPLETE)
{
RemovableDriveManager::get_instance().update(0, true);
}
if (m_initialized) {
// Signal the worker thread to wake up and enumerate removable drives.
m_wakeup = true;
m_thread_stop_condition.notify_all();
}
break;
default:
// Send all other messages on to the default windows handler.
lRet = DefWindowProc(hWnd, message, wParam, lParam);
break;
}
return lRet;
}
void RemovableDriveManager::register_window()
{
//creates new unvisible window that is recieving callbacks from system
// structure to register
// currently not used, left for possible future use
WNDCLASSEX wndClass;
wndClass.cbSize = sizeof(WNDCLASSEX);
wndClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
wndClass.hInstance = reinterpret_cast<HINSTANCE>(GetModuleHandle(0));
wndClass.lpfnWndProc = reinterpret_cast<WNDPROC>(WinProcCallback);//this is callback
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hIcon = LoadIcon(0, IDI_APPLICATION);
wndClass.hbrBackground = CreateSolidBrush(RGB(192, 192, 192));
wndClass.hCursor = LoadCursor(0, IDC_ARROW);
wndClass.lpszClassName = L"PrusaSlicer_aux_class";
wndClass.lpszMenuName = NULL;
wndClass.hIconSm = wndClass.hIcon;
if(!RegisterClassEx(&wndClass))
{
DWORD err = GetLastError();
return;
}
HWND hWnd = CreateWindowEx(
WS_EX_NOACTIVATE,
L"PrusaSlicer_aux_class",
L"PrusaSlicer_aux_wnd",
WS_DISABLED, // style
CW_USEDEFAULT, 0,
640, 480,
NULL, NULL,
GetModuleHandle(NULL),
NULL);
if(hWnd == NULL)
{
DWORD err = GetLastError();
}
//ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
}
#endif
#else
namespace search_for_drives_internal
@ -424,9 +347,7 @@ void RemovableDriveManager::init(wxEvtHandler *callback_evt_handler)
m_initialized = true;
m_callback_evt_handler = callback_evt_handler;
#if _WIN32
//this->register_window_msw();
#elif __APPLE__
#if __APPLE__
this->register_window_osx();
#endif
@ -440,6 +361,10 @@ void RemovableDriveManager::init(wxEvtHandler *callback_evt_handler)
void RemovableDriveManager::shutdown()
{
#if __APPLE__
this->unregister_window_osx();
#endif
#ifndef REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
if (m_thread.joinable()) {
// Stop the worker thread, if running.
@ -455,12 +380,6 @@ void RemovableDriveManager::shutdown()
}
#endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
#if _WIN32
//this->unregister_window_msw();
#elif __APPLE__
this->unregister_window_osx();
#endif
m_initialized = false;
m_callback_evt_handler = nullptr;
}
@ -493,6 +412,10 @@ RemovableDriveManager::RemovableDrivesStatus RemovableDriveManager::status()
void RemovableDriveManager::update()
{
tbb::mutex::scoped_lock inside_update_lock;
#ifdef _WIN32
// All wake up calls up to now are now consumed when the drive enumeration starts.
m_wakeup = false;
#endif // _WIN32
if (inside_update_lock.try_acquire(m_inside_update_mutex)) {
// Got the lock without waiting. That means, the update was not running.
// Run the update.
@ -516,12 +439,28 @@ void RemovableDriveManager::update()
#ifndef REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
void RemovableDriveManager::thread_proc()
{
// Signal the worker thread to update initially.
#ifdef _WIN32
m_wakeup = true;
#endif // _WIN32
for (;;) {
// Wait for 2 seconds before running the disk enumeration.
// Cancellable.
{
std::unique_lock<std::mutex> lck(m_thread_stop_mutex);
#ifdef _WIN32
// Windows do not send an update on insert / eject of an SD card into an external SD card reader.
// Windows also do not send an update on software eject of a FLASH drive.
// We can likely use the Windows WMI API, but it will be quite time consuming to implement.
// https://www.codeproject.com/Articles/10539/Making-WMI-Queries-In-C
// https://docs.microsoft.com/en-us/windows/win32/wmisdk/wmi-start-page
// https://docs.microsoft.com/en-us/windows/win32/wmisdk/com-api-for-wmi
// https://docs.microsoft.com/en-us/windows/win32/wmisdk/example--receiving-event-notifications-through-wmi-
m_thread_stop_condition.wait_for(lck, std::chrono::seconds(2), [this]{ return m_stop || m_wakeup; });
#else
m_thread_stop_condition.wait_for(lck, std::chrono::seconds(2), [this]{ return m_stop; });
#endif
}
if (m_stop)
// Stop the worker thread.

View file

@ -84,6 +84,11 @@ public:
// It would be better to make this method private and friend to RemovableDriveManagerMM, but RemovableDriveManagerMM is an ObjectiveC class.
void update();
#ifdef _WIN32
// Called by Win32 Volume arrived / detached callback.
void volumes_changed();
#endif // _WIN32
private:
bool m_initialized { false };
wxEvtHandler* m_callback_evt_handler { nullptr };
@ -95,6 +100,9 @@ private:
std::condition_variable m_thread_stop_condition;
mutable std::mutex m_thread_stop_mutex;
bool m_stop { false };
#ifdef _WIN32
std::atomic<bool> m_wakeup { false };
#endif /* _WIN32 */
#endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
// Called from update() to enumerate removable drives.
@ -114,10 +122,7 @@ private:
// Set with set_and_verify_last_save_path() to a removable drive path to be ejected.
std::string m_last_save_path;
#if _WIN32
//registers for notifications by creating invisible window
//void register_window_msw();
#elif __APPLE__
#if __APPLE__
void register_window_osx();
void unregister_window_osx();
void list_devices(std::vector<DriveData> &out) const;

View file

@ -129,8 +129,10 @@ void RemovableDriveManager::register_window_osx()
void RemovableDriveManager::unregister_window_osx()
{
if (m_impl_osx)
if (m_impl_osx) {
[m_impl_osx release];
m_impl_osx = nullptr;
}
}
namespace search_for_drives_internal

View file

@ -1231,6 +1231,7 @@ void TabPrint::build()
optgroup->append_single_option_line("skirts");
optgroup->append_single_option_line("skirt_distance");
optgroup->append_single_option_line("skirt_height");
optgroup->append_single_option_line("draft_shield");
optgroup->append_single_option_line("min_skirt_length");
optgroup = page->new_optgroup(_(L("Brim")));
@ -1417,7 +1418,10 @@ void TabPrint::update()
if (m_update_cnt==0) {
m_config_manipulation.toggle_print_fff_options(m_config);
wxGetApp().obj_list()->update_and_show_object_settings_item();
// update() could be called during undo/redo execution
// Update of objectList can cause a crash in this case (because m_objects doesn't match ObjectList)
if (!wxGetApp().plater()->inside_snapshot_capture())
wxGetApp().obj_list()->update_and_show_object_settings_item();
wxGetApp().mainframe->on_config_changed(m_config);
}
@ -3819,7 +3823,10 @@ void TabSLAPrint::update()
if (m_update_cnt == 0) {
m_config_manipulation.toggle_print_sla_options(m_config);
wxGetApp().obj_list()->update_and_show_object_settings_item();
// update() could be called during undo/redo execution
// Update of objectList can cause a crash in this case (because m_objects doesn't match ObjectList)
if (!wxGetApp().plater()->inside_snapshot_capture())
wxGetApp().obj_list()->update_and_show_object_settings_item();
wxGetApp().mainframe->on_config_changed(m_config);
}