Merge branch 'master' into wipe_tower_improvements

This commit is contained in:
Lukas Matena 2018-03-09 15:10:15 +01:00
commit 1c6fa6660e
79 changed files with 34645 additions and 10116 deletions

View file

@ -64,6 +64,8 @@ add_library(libslic3r STATIC
${LIBDIR}/libslic3r/Fill/FillConcentric.hpp
${LIBDIR}/libslic3r/Fill/FillHoneycomb.cpp
${LIBDIR}/libslic3r/Fill/FillHoneycomb.hpp
${LIBDIR}/libslic3r/Fill/FillGyroid.cpp
${LIBDIR}/libslic3r/Fill/FillGyroid.hpp
${LIBDIR}/libslic3r/Fill/FillPlanePath.cpp
${LIBDIR}/libslic3r/Fill/FillPlanePath.hpp
${LIBDIR}/libslic3r/Fill/FillRectilinear.cpp
@ -173,6 +175,8 @@ add_library(libslic3r_gui STATIC
${LIBDIR}/slic3r/GUI/3DScene.hpp
${LIBDIR}/slic3r/GUI/GLShader.cpp
${LIBDIR}/slic3r/GUI/GLShader.hpp
${LIBDIR}/slic3r/GUI/Preferences.cpp
${LIBDIR}/slic3r/GUI/Preferences.hpp
${LIBDIR}/slic3r/GUI/Preset.cpp
${LIBDIR}/slic3r/GUI/Preset.hpp
${LIBDIR}/slic3r/GUI/PresetBundle.cpp
@ -199,7 +203,12 @@ add_library(libslic3r_gui STATIC
${LIBDIR}/slic3r/GUI/WipeTowerDialog.hpp
${LIBDIR}/slic3r/GUI/RammingChart.cpp
${LIBDIR}/slic3r/GUI/RammingChart.hpp
${LIBDIR}/slic3r/Utils/Http.cpp
${LIBDIR}/slic3r/Utils/Http.hpp
${LIBDIR}/slic3r/Utils/OctoPrint.cpp
${LIBDIR}/slic3r/Utils/OctoPrint.hpp
${LIBDIR}/slic3r/Utils/Bonjour.cpp
${LIBDIR}/slic3r/Utils/Bonjour.hpp
)
add_library(admesh STATIC
@ -339,6 +348,7 @@ set(XS_XSP_FILES
${XSP_DIR}/Surface.xsp
${XSP_DIR}/SurfaceCollection.xsp
${XSP_DIR}/TriangleMesh.xsp
${XSP_DIR}/Utils_OctoPrint.xsp
${XSP_DIR}/XS.xsp
)
foreach (file ${XS_XSP_FILES})
@ -476,6 +486,7 @@ if(SLIC3R_STATIC)
# Use boost libraries linked statically to the C++ runtime.
# set(Boost_USE_STATIC_RUNTIME ON)
endif()
#set(Boost_DEBUG ON)
find_package(Boost REQUIRED COMPONENTS system filesystem thread log locale regex)
if(Boost_FOUND)
include_directories(${Boost_INCLUDE_DIRS})
@ -523,6 +534,27 @@ if (SLIC3R_PRUSACONTROL)
target_link_libraries(XS ${wxWidgets_LIBRARIES})
endif()
find_package(CURL REQUIRED)
include_directories(${CURL_INCLUDE_DIRS})
target_link_libraries(XS ${CURL_LIBRARIES})
if (SLIC3R_STATIC)
if (NOT APPLE)
# libcurl is always linked dynamically to the system libcurl on OSX.
# On other systems, libcurl is linked statically if SLIC3R_STATIC is set.
add_definitions(-DCURL_STATICLIB)
endif()
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
# As of now, our build system produces a statically linked libcurl,
# which links the OpenSSL library dynamically.
find_package(OpenSSL REQUIRED)
message("OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}")
message("OpenSSL libraries: ${OPENSSL_LIBRARIES}")
include_directories(${OPENSSL_INCLUDE_DIR})
target_link_libraries(XS ${OPENSSL_LIBRARIES})
endif()
endif()
## OPTIONAL packages
# Find eigen3 or use bundled version
@ -597,6 +629,17 @@ elseif (NOT MSVC)
target_link_libraries(slic3r -lstdc++)
endif ()
if (MSVC)
# Here we associate some additional properties with the MSVC projects to enable compilation and debugging out of the box.
# It seems a props file needs to be copied to the same dir as the proj file, otherwise MSVC doesn't load it up.
# For copying, the configure_file() function seems to work much better than the file() function.
configure_file("${PROJECT_SOURCE_DIR}/cmake/msvc/xs.wperl64d.props" ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
set_target_properties(XS PROPERTIES VS_USER_PROPS "xs.wperl64d.props")
configure_file("${PROJECT_SOURCE_DIR}/cmake/msvc/slic3r.wperl64d.props" ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
set_target_properties(slic3r PROPERTIES VS_USER_PROPS "slic3r.wperl64d.props")
endif ()
# Installation
install(TARGETS XS DESTINATION lib/slic3r-prusa3d/auto/Slic3r/XS)
install(FILES lib/Slic3r/XS.pm DESTINATION lib/slic3r-prusa3d/Slic3r)
install(TARGETS XS DESTINATION ${PERL_VENDORARCH}/auto/Slic3r/XS)
install(FILES lib/Slic3r/XS.pm DESTINATION ${PERL_VENDORLIB}/Slic3r)

View file

@ -107,6 +107,11 @@ stl_fix_normal_directions(stl_file *stl) {
struct stl_normal *newn;
struct stl_normal *temp;
int* reversed_ids;
int reversed_count = 0;
int id;
int force_exit = 0;
if (stl->error) return;
/* Initialize linked list. */
@ -121,13 +126,18 @@ stl_fix_normal_directions(stl_file *stl) {
norm_sw = (char*)calloc(stl->stats.number_of_facets, sizeof(char));
if(norm_sw == NULL) perror("stl_fix_normal_directions");
/* Initialize list that keeps track of reversed facets. */
reversed_ids = (int*)calloc(stl->stats.number_of_facets, sizeof(int));
if (reversed_ids == NULL) perror("stl_fix_normal_directions reversed_ids");
facet_num = 0;
/* If normal vector is not within tolerance and backwards:
Arbitrarily starts at face 0. If this one is wrong, we're screwed. Thankfully, the chances
of it being wrong randomly are low if most of the triangles are right: */
if(stl_check_normal_vector(stl, 0, 0) == 2)
stl_reverse_facet(stl, 0);
if (stl_check_normal_vector(stl, 0, 0) == 2) {
stl_reverse_facet(stl, 0);
reversed_ids[reversed_count++] = 0;
}
/* Say that we've fixed this facet: */
norm_sw[facet_num] = 1;
@ -140,8 +150,19 @@ stl_fix_normal_directions(stl_file *stl) {
/* Reverse the neighboring facets if necessary. */
if(stl->neighbors_start[facet_num].which_vertex_not[j] > 2) {
/* If the facet has a neighbor that is -1, it means that edge isn't shared by another facet */
if(stl->neighbors_start[facet_num].neighbor[j] != -1)
stl_reverse_facet(stl, stl->neighbors_start[facet_num].neighbor[j]);
if(stl->neighbors_start[facet_num].neighbor[j] != -1) {
if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] == 1) {
/* trying to modify a facet already marked as fixed, revert all changes made until now and exit (fixes: #716, #574, #413, #269, #262, #259, #230, #228, #206) */
for (id = reversed_count - 1; id >= 0; --id) {
stl_reverse_facet(stl, reversed_ids[id]);
}
force_exit = 1;
break;
} else {
stl_reverse_facet(stl, stl->neighbors_start[facet_num].neighbor[j]);
reversed_ids[reversed_count++] = stl->neighbors_start[facet_num].neighbor[j];
}
}
}
/* If this edge of the facet is connected: */
if(stl->neighbors_start[facet_num].neighbor[j] != -1) {
@ -156,6 +177,10 @@ stl_fix_normal_directions(stl_file *stl) {
}
}
}
/* an error occourred, quit the for loop and exit */
if (force_exit) break;
/* Get next facet to fix from top of list. */
if(head->next != tail) {
facet_num = head->next->facet_num;
@ -179,7 +204,8 @@ stl_fix_normal_directions(stl_file *stl) {
/* This is the first facet of the next part. */
facet_num = i;
if(stl_check_normal_vector(stl, i, 0) == 2) {
stl_reverse_facet(stl, i);
stl_reverse_facet(stl, i);
reversed_ids[reversed_count++] = i;
}
norm_sw[facet_num] = 1;
@ -192,6 +218,7 @@ stl_fix_normal_directions(stl_file *stl) {
}
free(head);
free(tail);
free(reversed_ids);
free(norm_sw);
}

View file

@ -26,6 +26,7 @@ enum ExtrusionRole {
erSupportMaterial,
erSupportMaterialInterface,
erWipeTower,
erCustom,
// Extrusion role for a collection with multiple extrusion roles.
erMixed,
};

View file

@ -8,6 +8,7 @@
#include "FillConcentric.hpp"
#include "FillHoneycomb.hpp"
#include "Fill3DHoneycomb.hpp"
#include "FillGyroid.hpp"
#include "FillPlanePath.hpp"
#include "FillRectilinear.hpp"
#include "FillRectilinear2.hpp"
@ -21,6 +22,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
case ipConcentric: return new FillConcentric();
case ipHoneycomb: return new FillHoneycomb();
case ip3DHoneycomb: return new Fill3DHoneycomb();
case ipGyroid: return new FillGyroid();
case ipRectilinear: return new FillRectilinear2();
// case ipRectilinear: return new FillRectilinear();
case ipLine: return new FillLine();

View file

@ -0,0 +1,150 @@
#include "../ClipperUtils.hpp"
#include "../PolylineCollection.hpp"
#include "../Surface.hpp"
#include <cmath>
#include <algorithm>
#include <iostream>
#include "FillGyroid.hpp"
namespace Slic3r {
static inline Polyline make_wave_vertical(
double width, double height, double x0,
double segmentSize, double scaleFactor,
double z_cos, double z_sin, bool flip)
{
Polyline polyline;
polyline.points.emplace_back(Point(coord_t(clamp(0., width, x0) * scaleFactor), 0));
double phase_offset_sin = (z_cos < 0 ? M_PI : 0) + M_PI;
double phase_offset_cos = (z_cos < 0 ? M_PI : 0) + M_PI + (flip ? M_PI : 0.);
for (double y = 0.; y < height + segmentSize; y += segmentSize) {
y = std::min(y, height);
double a = sin(y + phase_offset_sin);
double b = - z_cos;
double res = z_sin * cos(y + phase_offset_cos);
double r = sqrt(sqr(a) + sqr(b));
double x = clamp(0., width, asin(a/r) + asin(res/r) + M_PI + x0);
polyline.points.emplace_back(convert_to<Point>(Pointf(x, y) * scaleFactor));
}
if (flip)
std::reverse(polyline.points.begin(), polyline.points.end());
return polyline;
}
static inline Polyline make_wave_horizontal(
double width, double height, double y0,
double segmentSize, double scaleFactor,
double z_cos, double z_sin, bool flip)
{
Polyline polyline;
polyline.points.emplace_back(Point(0, coord_t(clamp(0., height, y0) * scaleFactor)));
double phase_offset_sin = (z_sin < 0 ? M_PI : 0) + (flip ? 0 : M_PI);
double phase_offset_cos = z_sin < 0 ? M_PI : 0.;
for (double x=0.; x < width + segmentSize; x += segmentSize) {
x = std::min(x, width);
double a = cos(x + phase_offset_cos);
double b = - z_sin;
double res = z_cos * sin(x + phase_offset_sin);
double r = sqrt(sqr(a) + sqr(b));
double y = clamp(0., height, asin(a/r) + asin(res/r) + 0.5 * M_PI + y0);
polyline.points.emplace_back(convert_to<Point>(Pointf(x, y) * scaleFactor));
}
if (flip)
std::reverse(polyline.points.begin(), polyline.points.end());
return polyline;
}
static Polylines make_gyroid_waves(double gridZ, double density, double layer_width, double width, double height)
{
double scaleFactor = scale_(layer_width) / density;
double segmentSize = 0.5 * density;
//scale factor for 5% : 8 712 388
// 1z = 10^-6 mm ?
double z = gridZ / scaleFactor;
double z_sin = sin(z);
double z_cos = cos(z);
Polylines result;
if (abs(z_sin) <= abs(z_cos)) {
// Vertical wave
double x0 = M_PI * (int)((- 0.5 * M_PI) / M_PI - 1.);
bool flip = ((int)(x0 / M_PI + 1.) & 1) != 0;
for (; x0 < width - 0.5 * M_PI; x0 += M_PI, flip = ! flip)
result.emplace_back(make_wave_vertical(width, height, x0, segmentSize, scaleFactor, z_cos, z_sin, flip));
} else {
// Horizontal wave
bool flip = true;
for (double y0 = 0.; y0 < width; y0 += M_PI, flip = !flip)
result.emplace_back(make_wave_horizontal(width, height, y0, segmentSize, scaleFactor, z_cos, z_sin, flip));
}
return result;
}
void FillGyroid::_fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out)
{
// no rotation is supported for this infill pattern
BoundingBox bb = expolygon.contour.bounding_box();
coord_t distance = coord_t(scale_(this->spacing) / (params.density*this->scaling));
// align bounding box to a multiple of our grid module
bb.merge(_align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)));
// generate pattern
Polylines polylines = make_gyroid_waves(
scale_(this->z),
params.density*this->scaling,
this->spacing,
ceil(bb.size().x / distance) + 1.,
ceil(bb.size().y / distance) + 1.);
// move pattern in place
for (Polyline &polyline : polylines)
polyline.translate(bb.min.x, bb.min.y);
// clip pattern to boundaries
polylines = intersection_pl(polylines, (Polygons)expolygon);
// connect lines
if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections
ExPolygon expolygon_off;
{
ExPolygons expolygons_off = offset_ex(expolygon, (float)SCALED_EPSILON);
if (! expolygons_off.empty()) {
// When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island.
assert(expolygons_off.size() == 1);
std::swap(expolygon_off, expolygons_off.front());
}
}
Polylines chained = PolylineCollection::chained_path_from(
std::move(polylines),
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
bool first = true;
for (Polyline &polyline : chained) {
if (! first) {
// Try to connect the lines.
Points &pts_end = polylines_out.back().points;
const Point &first_point = polyline.points.front();
const Point &last_point = pts_end.back();
// TODO: we should also check that both points are on a fill_boundary to avoid
// connecting paths on the boundaries of internal regions
// TODO: avoid crossing current infill path
if (first_point.distance_to(last_point) <= 5 * distance &&
expolygon_off.contains(Line(last_point, first_point))) {
// Append the polyline.
pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
continue;
}
}
// The lines cannot be connected.
polylines_out.emplace_back(std::move(polyline));
first = false;
}
}
}
} // namespace Slic3r

View file

@ -0,0 +1,34 @@
#ifndef slic3r_FillGyroid_hpp_
#define slic3r_FillGyroid_hpp_
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
class FillGyroid : public Fill
{
public:
FillGyroid() {}
virtual Fill* clone() const { return new FillGyroid(*this); }
// require bridge flow since most of this pattern hangs in air
virtual bool use_bridge_flow() const { return true; }
protected:
// mult of density, to have a good %of weight for each density parameter
float scaling = 1.75;
virtual void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out);
};
} // namespace Slic3r
#endif // slic3r_FillGyroid_hpp_

View file

@ -587,6 +587,15 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true);
// Set extruder(s) temperature before and after start G-code.
this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false);
if (m_enable_analyzer)
{
// adds tag for analyzer
char buf[32];
sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
_writeln(file, buf);
}
// Write the custom start G-code
_writeln(file, start_gcode);
// Process filament-specific gcode in extruder order.
@ -770,6 +779,15 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
// Write end commands to file.
_write(file, this->retract());
_write(file, m_writer.set_fan(false));
if (m_enable_analyzer)
{
// adds tag for analyzer
char buf[32];
sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
_writeln(file, buf);
}
// Process filament-specific gcode in extruder order.
if (print.config.single_extruder_multi_material) {
// Process the end_filament_gcode for the active filament only.
@ -1384,18 +1402,13 @@ void GCode::apply_print_config(const PrintConfig &print_config)
void GCode::append_full_config(const Print& print, std::string& str)
{
char buff[4096];
const StaticPrintConfig *configs[] = { &print.config, &print.default_object_config, &print.default_region_config };
for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); ++i) {
const StaticPrintConfig *cfg = configs[i];
for (const std::string &key : cfg->keys())
{
if (key != "compatible_printers")
{
sprintf(buff, "; %s = %s\n", key.c_str(), cfg->serialize(key).c_str());
str += buff;
}
str += "; " + key + " = " + cfg->serialize(key) + "\n";
}
}
}

View file

@ -150,7 +150,13 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi
{
// processes 'special' comments contained in line
if (_process_tags(line))
{
#if 0
// DEBUG ONLY: puts the line back into the gcode
m_process_output += line.raw() + "\n";
#endif
return;
}
// sets new start position/extrusion
_set_start_position(_get_end_position());

View file

@ -1,6 +1,8 @@
#include "Analyzer.hpp"
#include "PreviewData.hpp"
#include <float.h>
#include <wx/intl.h>
#include "slic3r/GUI/GUI.hpp"
namespace Slic3r {
@ -125,26 +127,28 @@ const GCodePreviewData::Color GCodePreviewData::Extrusion::Default_Extrusion_Rol
Color(0.0f, 0.5f, 0.0f, 1.0f), // erSupportMaterial
Color(0.0f, 0.0f, 0.5f, 1.0f), // erSupportMaterialInterface
Color(0.7f, 0.89f, 0.67f, 1.0f), // erWipeTower
Color(1.0f, 1.0f, 0.0f, 1.0f), // erCustom
Color(0.0f, 0.0f, 0.0f, 1.0f) // erMixed
};
// todo: merge with Slic3r::ExtrusionRole2String() from GCode.cpp
const std::string GCodePreviewData::Extrusion::Default_Extrusion_Role_Names[Num_Extrusion_Roles]
{
"None",
"Perimeter",
"External perimeter",
"Overhang perimeter",
"Internal infill",
"Solid infill",
"Top solid infill",
"Bridge infill",
"Gap fill",
"Skirt",
"Support material",
"Support material interface",
"Wipe tower",
"Mixed"
L("None"),
L("Perimeter"),
L("External perimeter"),
L("Overhang perimeter"),
L("Internal infill"),
L("Solid infill"),
L("Top solid infill"),
L("Bridge infill"),
L("Gap fill"),
L("Skirt"),
L("Support material"),
L("Support material interface"),
L("Wipe tower"),
L("Custom"),
L("Mixed")
};
const GCodePreviewData::Extrusion::EViewType GCodePreviewData::Extrusion::Default_View_Type = GCodePreviewData::Extrusion::FeatureType;
@ -323,15 +327,15 @@ std::string GCodePreviewData::get_legend_title() const
switch (extrusion.view_type)
{
case Extrusion::FeatureType:
return "Feature type";
return L("Feature type");
case Extrusion::Height:
return "Height (mm)";
return L("Height (mm)");
case Extrusion::Width:
return "Width (mm)";
return L("Width (mm)");
case Extrusion::Feedrate:
return "Speed (mm/s)";
return L("Speed (mm/s)");
case Extrusion::Tool:
return "Tool";
return L("Tool");
}
return "";
@ -360,10 +364,13 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::
{
case Extrusion::FeatureType:
{
items.reserve(erMixed - erPerimeter + 1);
for (unsigned int i = (unsigned int)erPerimeter; i < (unsigned int)erMixed; ++i)
ExtrusionRole first_valid = erPerimeter;
ExtrusionRole last_valid = erCustom;
items.reserve(last_valid - first_valid + 1);
for (unsigned int i = (unsigned int)first_valid; i <= (unsigned int)last_valid; ++i)
{
items.emplace_back(extrusion.role_names[i], extrusion.role_colors[i]);
items.emplace_back(_CHB(extrusion.role_names[i].c_str()).data(), extrusion.role_colors[i]);
}
break;
@ -389,8 +396,8 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::
items.reserve(tools_colors_count);
for (unsigned int i = 0; i < tools_colors_count; ++i)
{
char buf[32];
sprintf(buf, "Extruder %d", i + 1);
char buf[MIN_BUF_LENGTH_FOR_L];
sprintf(buf, _CHB(L("Extruder %d")), i + 1);
GCodePreviewData::Color color;
::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + i * 4), 4 * sizeof(float));

View file

@ -19,6 +19,18 @@ static const float DEFAULT_EXTRUDE_FACTOR_OVERRIDE_PERCENTAGE = 1.0f; // 100 per
static const float PREVIOUS_FEEDRATE_THRESHOLD = 0.0001f;
#if ENABLE_MOVE_STATS
static const std::string MOVE_TYPE_STR[Slic3r::GCodeTimeEstimator::Block::Num_Types] =
{
"Noop",
"Retract",
"Unretract",
"Tool_change",
"Move",
"Extrude"
};
#endif // ENABLE_MOVE_STATS
namespace Slic3r {
void GCodeTimeEstimator::Feedrates::reset()
@ -139,6 +151,14 @@ namespace Slic3r {
return (acceleration == 0.0f) ? 0.0f : (2.0f * acceleration * distance - sqr(initial_rate) + sqr(final_rate)) / (4.0f * acceleration);
}
#if ENABLE_MOVE_STATS
GCodeTimeEstimator::MoveStats::MoveStats()
: count(0)
, time(0.0f)
{
}
#endif // ENABLE_MOVE_STATS
GCodeTimeEstimator::GCodeTimeEstimator()
{
reset();
@ -155,6 +175,10 @@ namespace Slic3r {
_calculate_time();
#if ENABLE_MOVE_STATS
_log_moves_stats();
#endif // ENABLE_MOVE_STATS
_reset_blocks();
_reset();
}
@ -166,6 +190,10 @@ namespace Slic3r {
_parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2));
_calculate_time();
#if ENABLE_MOVE_STATS
_log_moves_stats();
#endif // ENABLE_MOVE_STATS
_reset_blocks();
_reset();
}
@ -180,6 +208,10 @@ namespace Slic3r {
_parser.parse_line(line, action);
_calculate_time();
#if ENABLE_MOVE_STATS
_log_moves_stats();
#endif // ENABLE_MOVE_STATS
_reset_blocks();
_reset();
}
@ -208,6 +240,11 @@ namespace Slic3r {
{
PROFILE_FUNC();
_calculate_time();
#if ENABLE_MOVE_STATS
_log_moves_stats();
#endif // ENABLE_MOVE_STATS
_reset_blocks();
_reset();
}
@ -393,6 +430,9 @@ namespace Slic3r {
void GCodeTimeEstimator::reset()
{
_time = 0.0f;
#if ENABLE_MOVE_STATS
_moves_stats.clear();
#endif // ENABLE_MOVE_STATS
_reset_blocks();
_reset();
}
@ -448,9 +488,24 @@ namespace Slic3r {
for (const Block& block : _blocks)
{
#if ENABLE_MOVE_STATS
float block_time = 0.0f;
block_time += block.acceleration_time();
block_time += block.cruise_time();
block_time += block.deceleration_time();
_time += block_time;
MovesStatsMap::iterator it = _moves_stats.find(block.move_type);
if (it == _moves_stats.end())
it = _moves_stats.insert(MovesStatsMap::value_type(block.move_type, MoveStats())).first;
it->second.count += 1;
it->second.time += block_time;
#else
_time += block.acceleration_time();
_time += block.cruise_time();
_time += block.deceleration_time();
#endif // ENABLE_MOVE_STATS
}
}
@ -746,6 +801,28 @@ namespace Slic3r {
set_axis_position((EAxis)a, new_pos[a]);
}
#if ENABLE_MOVE_STATS
// detects block move type
block.move_type = Block::Noop;
if (block.delta_pos[E] < 0.0f)
{
if ((block.delta_pos[X] != 0.0f) || (block.delta_pos[Y] != 0.0f) || (block.delta_pos[Z] != 0.0f))
block.move_type = Block::Move;
else
block.move_type = Block::Retract;
}
else if (block.delta_pos[E] > 0.0f)
{
if ((block.delta_pos[X] == 0.0f) && (block.delta_pos[Y] == 0.0f) && (block.delta_pos[Z] == 0.0f))
block.move_type = Block::Unretract;
else if ((block.delta_pos[X] != 0.0f) || (block.delta_pos[Y] != 0.0f))
block.move_type = Block::Extrude;
}
else if ((block.delta_pos[X] != 0.0f) || (block.delta_pos[Y] != 0.0f) || (block.delta_pos[Z] != 0.0f))
block.move_type = Block::Move;
#endif // ENABLE_MOVE_STATS
// adds block to blocks list
_blocks.emplace_back(block);
}
@ -1064,4 +1141,24 @@ namespace Slic3r {
next->flags.recalculate = false;
}
}
#if ENABLE_MOVE_STATS
void GCodeTimeEstimator::_log_moves_stats() const
{
float moves_count = 0.0f;
for (const MovesStatsMap::value_type& move : _moves_stats)
{
moves_count += (float)move.second.count;
}
for (const MovesStatsMap::value_type& move : _moves_stats)
{
std::cout << MOVE_TYPE_STR[move.first];
std::cout << ": count " << move.second.count << " (" << 100.0f * (float)move.second.count / moves_count << "%)";
std::cout << " - time: " << move.second.time << "s (" << 100.0f * move.second.time / _time << "%)";
std::cout << std::endl;
}
std::cout << std::endl;
}
#endif // ENABLE_MOVE_STATS
}

View file

@ -5,6 +5,8 @@
#include "PrintConfig.hpp"
#include "GCodeReader.hpp"
#define ENABLE_MOVE_STATS 0
namespace Slic3r {
//
@ -74,6 +76,19 @@ namespace Slic3r {
public:
struct Block
{
#if ENABLE_MOVE_STATS
enum EMoveType : unsigned char
{
Noop,
Retract,
Unretract,
Tool_change,
Move,
Extrude,
Num_Types
};
#endif // ENABLE_MOVE_STATS
struct FeedrateProfile
{
float entry; // mm/s
@ -106,6 +121,10 @@ namespace Slic3r {
bool nominal_length;
};
#if ENABLE_MOVE_STATS
EMoveType move_type;
#endif // ENABLE_MOVE_STATS
Flags flags;
float delta_pos[Num_Axis]; // mm
@ -156,6 +175,18 @@ namespace Slic3r {
typedef std::vector<Block> BlocksList;
#if ENABLE_MOVE_STATS
struct MoveStats
{
unsigned int count;
float time;
MoveStats();
};
typedef std::map<Block::EMoveType, MoveStats> MovesStatsMap;
#endif // ENABLE_MOVE_STATS
private:
GCodeReader _parser;
State _state;
@ -163,6 +194,9 @@ namespace Slic3r {
Feedrates _prev;
BlocksList _blocks;
float _time; // s
#if ENABLE_MOVE_STATS
MovesStatsMap _moves_stats;
#endif // ENABLE_MOVE_STATS
public:
GCodeTimeEstimator();
@ -318,6 +352,10 @@ namespace Slic3r {
void _planner_reverse_pass_kernel(Block& curr, Block& next);
void _recalculate_trapezoids();
#if ENABLE_MOVE_STATS
void _log_moves_stats() const;
#endif // ENABLE_MOVE_STATS
};
} /* namespace Slic3r */

View file

@ -12,6 +12,7 @@
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/nowide/iostream.hpp>
#include <boost/algorithm/string/replace.hpp>
namespace Slic3r {
@ -91,7 +92,15 @@ Model Model::read_from_archive(const std::string &input_file, PresetBundle* bund
throw std::runtime_error("The supplied file couldn't be read because it's empty");
for (ModelObject *o : model.objects)
o->input_file = input_file;
{
if (boost::algorithm::iends_with(input_file, ".zip.amf"))
{
// we remove the .zip part of the extension to avoid it be added to filenames when exporting
o->input_file = boost::ireplace_last_copy(input_file, ".zip.", ".");
}
else
o->input_file = input_file;
}
if (add_default_instances)
model.add_default_instances();

File diff suppressed because it is too large Load diff

View file

@ -29,7 +29,7 @@ enum GCodeFlavor {
enum InfillPattern {
ipRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral,
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral,
};
enum SupportMaterialPattern {
@ -73,6 +73,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum<InfillPattern>::get_enu
keys_map["concentric"] = ipConcentric;
keys_map["honeycomb"] = ipHoneycomb;
keys_map["3dhoneycomb"] = ip3DHoneycomb;
keys_map["gyroid"] = ipGyroid;
keys_map["hilbertcurve"] = ipHilbertCurve;
keys_map["archimedeanchords"] = ipArchimedeanChords;
keys_map["octagramspiral"] = ipOctagramSpiral;
@ -699,6 +700,7 @@ class HostConfig : public StaticPrintConfig
public:
ConfigOptionString octoprint_host;
ConfigOptionString octoprint_apikey;
ConfigOptionString octoprint_cafile;
ConfigOptionString serial_port;
ConfigOptionInt serial_speed;
@ -707,6 +709,7 @@ protected:
{
OPT_PTR(octoprint_host);
OPT_PTR(octoprint_apikey);
OPT_PTR(octoprint_cafile);
OPT_PTR(serial_port);
OPT_PTR(serial_speed);
}

View file

@ -198,9 +198,8 @@ TriangleMesh::repair() {
stl_clear_error(&stl);
}
// commenting out the following call fixes: #574, #413, #269, #262, #259, #230, #228, #206
// // normal_directions
// stl_fix_normal_directions(&stl);
// normal_directions
stl_fix_normal_directions(&stl);
// normal_values
stl_fix_normal_values(&stl);
@ -210,7 +209,7 @@ TriangleMesh::repair() {
// neighbors
stl_verify_neighbors(&stl);
this->repaired = true;
BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished";
@ -1187,40 +1186,46 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slic
loops correctly in some edge cases when original model had overlapping facets
*/
std::vector<double> area;
std::vector<size_t> sorted_area; // vector of indices
for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++ loop) {
area.push_back(loop->area());
sorted_area.push_back(loop - loops.begin());
}
// outer first
std::sort(sorted_area.begin(), sorted_area.end(),
[&area](size_t a, size_t b) { return std::abs(area[a]) > std::abs(area[b]); });
/* The following lines are commented out because they can generate wrong polygons,
see for example issue #661 */
// we don't perform a safety offset now because it might reverse cw loops
Polygons p_slices;
for (std::vector<size_t>::const_iterator loop_idx = sorted_area.begin(); loop_idx != sorted_area.end(); ++ loop_idx) {
/* we rely on the already computed area to determine the winding order
of the loops, since the Orientation() function provided by Clipper
would do the same, thus repeating the calculation */
Polygons::const_iterator loop = loops.begin() + *loop_idx;
if (area[*loop_idx] > +EPSILON)
p_slices.push_back(*loop);
else if (area[*loop_idx] < -EPSILON)
//FIXME This is arbitrary and possibly very slow.
// If the hole is inside a polygon, then there is no need to diff.
// If the hole intersects a polygon boundary, then diff it, but then
// there is no guarantee of an ordering of the loops.
// Maybe we can test for the intersection before running the expensive diff algorithm?
p_slices = diff(p_slices, *loop);
}
//std::vector<double> area;
//std::vector<size_t> sorted_area; // vector of indices
//for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++ loop) {
// area.push_back(loop->area());
// sorted_area.push_back(loop - loops.begin());
//}
//
//// outer first
//std::sort(sorted_area.begin(), sorted_area.end(),
// [&area](size_t a, size_t b) { return std::abs(area[a]) > std::abs(area[b]); });
//// we don't perform a safety offset now because it might reverse cw loops
//Polygons p_slices;
//for (std::vector<size_t>::const_iterator loop_idx = sorted_area.begin(); loop_idx != sorted_area.end(); ++ loop_idx) {
// /* we rely on the already computed area to determine the winding order
// of the loops, since the Orientation() function provided by Clipper
// would do the same, thus repeating the calculation */
// Polygons::const_iterator loop = loops.begin() + *loop_idx;
// if (area[*loop_idx] > +EPSILON)
// p_slices.push_back(*loop);
// else if (area[*loop_idx] < -EPSILON)
// //FIXME This is arbitrary and possibly very slow.
// // If the hole is inside a polygon, then there is no need to diff.
// // If the hole intersects a polygon boundary, then diff it, but then
// // there is no guarantee of an ordering of the loops.
// // Maybe we can test for the intersection before running the expensive diff algorithm?
// p_slices = diff(p_slices, *loop);
//}
// perform a safety offset to merge very close facets (TODO: find test case for this)
double safety_offset = scale_(0.0499);
//FIXME see https://github.com/prusa3d/Slic3r/issues/520
// double safety_offset = scale_(0.0001);
ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset);
/* The following line is commented out because it can generate wrong polygons,
see for example issue #661 */
//ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset);
#ifdef SLIC3R_TRIANGLEMESH_DEBUG
size_t holes_count = 0;
@ -1231,7 +1236,10 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slic
#endif
// append to the supplied collection
expolygons_append(*slices, ex_slices);
/* Fix for issue #661 { */
expolygons_append(*slices, offset2_ex(union_(loops, false), +safety_offset, -safety_offset));
//expolygons_append(*slices, ex_slices);
/* } */
}
void TriangleMeshSlicer::make_expolygons(std::vector<IntersectionLine> &lines, ExPolygons* slices) const

View file

@ -64,6 +64,7 @@ REGISTER_CLASS(PresetCollection, "GUI::PresetCollection");
REGISTER_CLASS(PresetBundle, "GUI::PresetBundle");
REGISTER_CLASS(PresetHints, "GUI::PresetHints");
REGISTER_CLASS(TabIface, "GUI::Tab");
REGISTER_CLASS(OctoPrint, "OctoPrint");
SV* ConfigBase__as_hash(ConfigBase* THIS)
{

View file

@ -27,6 +27,8 @@
#include <wx/image.h>
#include <wx/settings.h>
#include "GUI.hpp"
namespace Slic3r {
void GLIndexedVertexArray::load_mesh_flat_shading(const TriangleMesh &mesh)
@ -454,6 +456,25 @@ void GLVolumeCollection::render_legacy() const
glDisableClientState(GL_NORMAL_ARRAY);
}
std::vector<double> GLVolumeCollection::get_current_print_zs() const
{
std::vector<double> print_zs;
for (GLVolume *vol : this->volumes)
{
for (coordf_t z : vol->print_zs)
{
double round_z = (double)round(z * 100000.0f) / 100000.0f;
if (std::find(print_zs.begin(), print_zs.end(), round_z) == print_zs.end())
print_zs.push_back(round_z);
}
}
std::sort(print_zs.begin(), print_zs.end());
return print_zs;
}
// caller is responsible for supplying NO lines with zero length
static void thick_lines_to_indexed_vertex_array(
const Lines &lines,
@ -1129,7 +1150,7 @@ bool _3DScene::LegendTexture::generate(const GCodePreviewData& preview_data, con
m_data.clear();
// collects items to render
const std::string& title = preview_data.get_legend_title();
auto title = GUI::L_str(preview_data.get_legend_title());
const GCodePreviewData::LegendItemsList& items = preview_data.get_legend_items(tool_colors);
unsigned int items_count = (unsigned int)items.size();
@ -1151,7 +1172,7 @@ bool _3DScene::LegendTexture::generate(const GCodePreviewData& preview_data, con
unsigned int max_text_height = 0;
for (const GCodePreviewData::LegendItem& item : items)
{
memDC.GetTextExtent(item.text, &w, &h);
memDC.GetTextExtent(GUI::from_u8(item.text), &w, &h);
max_text_width = std::max(max_text_width, (unsigned int)w);
max_text_height = std::max(max_text_height, (unsigned int)h);
}
@ -1228,7 +1249,7 @@ bool _3DScene::LegendTexture::generate(const GCodePreviewData& preview_data, con
memDC.DrawRectangle(wxRect(icon_x_inner, icon_y + 1, px_inner_square, px_inner_square));
// draw text
memDC.DrawText(item.text, text_x, icon_y + text_y_offset);
memDC.DrawText(GUI::from_u8(item.text), text_x, icon_y + text_y_offset);
// update y
icon_y += icon_y_step;
@ -2209,6 +2230,9 @@ void _3DScene::_update_gcode_volumes_visibility(const GCodePreviewData& preview_
{
case GCodePreviewVolumeIndex::Extrusion:
{
if ((ExtrusionRole)s_gcode_preview_volume_index.first_volumes[i].flag == erCustom)
volume->zoom_to_volumes = false;
volume->is_active = preview_data.extrusion.is_role_flag_set((ExtrusionRole)s_gcode_preview_volume_index.first_volumes[i].flag);
break;
}

View file

@ -372,6 +372,9 @@ public:
void set_render_interleaved_only_volumes(const RenderInterleavedOnlyVolumes& render_interleaved_only_volumes) { _render_interleaved_only_volumes = render_interleaved_only_volumes; }
// Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection
std::vector<double> get_current_print_zs() const;
private:
GLVolumeCollection(const GLVolumeCollection &other);
GLVolumeCollection& operator=(const GLVolumeCollection &);

View file

@ -36,43 +36,43 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt)
{
// on_change(nullptr);
auto box = new wxStaticBox(this, wxID_ANY, _L("Shape"));
auto box = new wxStaticBox(this, wxID_ANY, _(L("Shape")));
auto sbsizer = new wxStaticBoxSizer(box, wxVERTICAL);
// shape options
m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(300, -1), wxCHB_TOP);
sbsizer->Add(m_shape_options_book);
auto optgroup = init_shape_options_page(_L("Rectangular"));
auto optgroup = init_shape_options_page(_(L("Rectangular")));
ConfigOptionDef def;
def.type = coPoints;
def.default_value = new ConfigOptionPoints{ Pointf(200, 200) };
def.label = _LU8("Size");
def.tooltip = _LU8("Size in X and Y of the rectangular plate.");
def.label = L("Size");
def.tooltip = L("Size in X and Y of the rectangular plate.");
Option option(def, "rect_size");
optgroup->append_single_option_line(option);
def.type = coPoints;
def.default_value = new ConfigOptionPoints{ Pointf(0, 0) };
def.label = _LU8("Origin");
def.tooltip = _LU8("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.");
def.label = L("Origin");
def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.");
option = Option(def, "rect_origin");
optgroup->append_single_option_line(option);
optgroup = init_shape_options_page(_L("Circular"));
optgroup = init_shape_options_page(_(L("Circular")));
def.type = coFloat;
def.default_value = new ConfigOptionFloat(200);
def.sidetext = _LU8("mm");
def.label = _LU8("Diameter");
def.tooltip = _LU8("Diameter of the print bed. It is assumed that origin (0,0) is located in the center.");
def.sidetext = L("mm");
def.label = L("Diameter");
def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center.");
option = Option(def, "diameter");
optgroup->append_single_option_line(option);
optgroup = init_shape_options_page(_L("Custom"));
optgroup = init_shape_options_page(_(L("Custom")));
Line line{ "", "" };
line.full_width = 1;
line.widget = [this](wxWindow* parent) {
auto btn = new wxButton(parent, wxID_ANY, _L("Load shape from STL..."), wxDefaultPosition, wxDefaultSize);
auto btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL...")), wxDefaultPosition, wxDefaultSize);
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn);
@ -117,7 +117,7 @@ ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(wxString title){
auto panel = new wxPanel(m_shape_options_book);
ConfigOptionsGroupShp optgroup;
optgroup = std::make_shared<ConfigOptionsGroup>(panel, _L("Settings"));
optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Settings")));
optgroup->label_width = 100;
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
@ -290,12 +290,12 @@ void BedShapePanel::update_shape()
void BedShapePanel::load_stl()
{
t_file_wild_card vec_FILE_WILDCARDS = get_file_wild_card();
std::vector<std::string> file_types = { "known", "stl", "obj", "amf", "prusa"};
wxString MODEL_WILDCARD;
std::vector<std::string> file_types = { "known", "stl", "obj", "amf", "3mf", "prusa" };
wxString MODEL_WILDCARD;
for (auto file_type: file_types)
MODEL_WILDCARD += vec_FILE_WILDCARDS.at(file_type) + "|";
auto dialog = new wxFileDialog(this, _L("Choose a file to import bed shape from (STL/OBJ/AMF/PRUSA):"), "", "",
auto dialog = new wxFileDialog(this, _(L("Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):")), "", "",
MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog->ShowModal() != wxID_OK) {
dialog->Destroy();
@ -312,7 +312,7 @@ void BedShapePanel::load_stl()
model = Model::read_from_file(file_name);
}
catch (std::exception &e) {
auto msg = _L("Error! ") + file_name + " : " + e.what() + ".";
auto msg = _(L("Error! ")) + file_name + " : " + e.what() + ".";
show_error(this, msg);
exit(1);
}
@ -321,11 +321,11 @@ void BedShapePanel::load_stl()
auto expolygons = mesh.horizontal_projection();
if (expolygons.size() == 0) {
show_error(this, _L("The selected file contains no geometry."));
show_error(this, _(L("The selected file contains no geometry.")));
return;
}
if (expolygons.size() > 1) {
show_error(this, _L("The selected file contains several disjoint areas. This is not supported."));
show_error(this, _(L("The selected file contains several disjoint areas. This is not supported.")));
return;
}

View file

@ -39,7 +39,7 @@ class BedShapeDialog : public wxDialog
{
BedShapePanel* m_panel;
public:
BedShapeDialog(wxWindow* parent) : wxDialog(parent, wxID_ANY, _L("Bed Shape"),
BedShapeDialog(wxWindow* parent) : wxDialog(parent, wxID_ANY, _(L("Bed Shape")),
wxDefaultPosition, wxSize(350, 700), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER){}
~BedShapeDialog(){ }

View file

@ -10,13 +10,20 @@
namespace Slic3r { namespace GUI {
wxString double_to_string(double const value)
{
int precision = 10 * value - int(10 * value) == 0 ? 1 : 2;
return value - int(value) == 0 ?
wxString::Format(_T("%i"), int(value)) :
wxNumberFormatter::ToString(value, precision, wxNumberFormatter::Style_None);
}
void Field::on_kill_focus(wxEvent& event) {
// Without this, there will be nasty focus bugs on Windows.
// Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all
// non-command events to allow the default handling to take place."
event.Skip();
std::cerr << "calling Field::on_kill_focus from " << m_opt_id<< "\n";
// call the registered function if it is available
// call the registered function if it is available
if (m_on_kill_focus!=nullptr)
m_on_kill_focus();
}
@ -30,9 +37,9 @@ namespace Slic3r { namespace GUI {
wxString Field::get_tooltip_text(const wxString& default_string)
{
wxString tooltip_text("");
wxString tooltip = wxString::FromUTF8(m_opt.tooltip.c_str());
wxString tooltip = L_str(m_opt.tooltip);
if (tooltip.length() > 0)
tooltip_text = tooltip + "(" + _L("default") + ": " +
tooltip_text = tooltip + "(" + _(L("default")) + ": " +
(boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") +
default_string + ")";
@ -45,7 +52,7 @@ namespace Slic3r { namespace GUI {
return std::regex_match(string, regex_pattern);
}
boost::any Field::get_value_by_opt_type(wxString str, ConfigOptionType type)
boost::any Field::get_value_by_opt_type(wxString str)
{
boost::any ret_val;
switch (m_opt.type){
@ -56,23 +63,17 @@ namespace Slic3r { namespace GUI {
case coPercents:
case coFloats:
case coFloat:{
if (m_opt.type == coPercent) str.RemoveLast();
if (m_opt.type == coPercent && str.Last() == '%')
str.RemoveLast();
double val;
str.ToCDouble(&val);
ret_val = val;
break; }
case coString:
case coStrings:
case coFloatOrPercent:
ret_val = str.ToStdString();
break;
case coFloatOrPercent:{
if (str.Last() == '%')
str.RemoveLast();
double val;
str.ToCDouble(&val);
ret_val = val;
break;
}
default:
break;
}
@ -90,13 +91,9 @@ namespace Slic3r { namespace GUI {
switch (m_opt.type) {
case coFloatOrPercent:
{
if (static_cast<const ConfigOptionFloatOrPercent*>(m_opt.default_value)->percent)
{
text_value = wxString::Format(_T("%i"), int(m_opt.default_value->getFloat()));
text_value += "%";
}
else
text_value = wxNumberFormatter::ToString(m_opt.default_value->getFloat(), 2);
text_value = double_to_string(m_opt.default_value->getFloat());
if (static_cast<const ConfigOptionFloatOrPercent*>(m_opt.default_value)->percent)
text_value += "%";
break;
}
case coPercent:
@ -106,29 +103,15 @@ namespace Slic3r { namespace GUI {
break;
}
case coPercents:
{
const ConfigOptionPercents *vec = static_cast<const ConfigOptionPercents*>(m_opt.default_value);
if (vec == nullptr || vec->empty()) break;
if (vec->size() > 1)
break;
double val = vec->get_at(0);
text_value = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None);
break;
}
case coFloats:
case coFloat:
{
double val = m_opt.default_value->getFloat();
text_value = (val - int(val)) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None);
break;
}
case coFloats:
{
const ConfigOptionFloats *vec = static_cast<const ConfigOptionFloats*>(m_opt.default_value);
if (vec == nullptr || vec->empty()) break;
if (vec->size() > 1)
break;
double val = vec->get_at(0);
text_value = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None);
double val = m_opt.type == coFloats ?
static_cast<const ConfigOptionFloats*>(m_opt.default_value)->get_at(0) :
m_opt.type == coFloat ?
m_opt.default_value->getFloat() :
static_cast<const ConfigOptionPercents*>(m_opt.default_value)->get_at(0);
text_value = double_to_string(val);
break;
}
case coString:
@ -174,7 +157,7 @@ namespace Slic3r { namespace GUI {
boost::any TextCtrl::get_value()
{
wxString ret_str = static_cast<wxTextCtrl*>(window)->GetValue();
boost::any ret_val = get_value_by_opt_type(ret_str, m_opt.type);
boost::any ret_val = get_value_by_opt_type(ret_str);
return ret_val;
}
@ -303,7 +286,7 @@ void Choice::set_selection()
break;
++idx;
}
if (m_opt.type == coPercent) text_value += "%";
// if (m_opt.type == coPercent) text_value += "%";
idx == m_opt.enum_values.size() ?
dynamic_cast<wxComboBox*>(window)->SetValue(text_value) :
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
@ -387,7 +370,7 @@ void Choice::set_value(boost::any value)
break;
++idx;
}
if (m_opt.type == coPercent) text_value += "%";
// if (m_opt.type == coPercent) text_value += "%";
idx == m_opt.enum_values.size() ?
dynamic_cast<wxComboBox*>(window)->SetValue(text_value) :
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
@ -429,7 +412,7 @@ boost::any Choice::get_value()
wxString ret_str = static_cast<wxComboBox*>(window)->GetValue();
if (m_opt.type != coEnum)
ret_val = get_value_by_opt_type(ret_str, m_opt.type);
ret_val = get_value_by_opt_type(ret_str);
else
{
int ret_enum = static_cast<wxComboBox*>(window)->GetSelection();
@ -535,21 +518,29 @@ void PointCtrl::set_value(const Pointf value)
void PointCtrl::set_value(boost::any value)
{
Pointf pt;
try
Pointf *ptf = boost::any_cast<Pointf>(&value);
if (!ptf)
{
pt = boost::any_cast<ConfigOptionPoints*>(value)->values.at(0);
ConfigOptionPoints* pts = boost::any_cast<ConfigOptionPoints*>(value);
pt = pts->values.at(0);
}
catch (const std::exception &e)
{
try{
pt = boost::any_cast<Pointf>(value);
}
catch (const std::exception &e)
{
std::cerr << "Error! Can't cast PointCtrl value" << m_opt_id << "\n";
return;
}
}
else
pt = *ptf;
// try
// {
// pt = boost::any_cast<ConfigOptionPoints*>(value)->values.at(0);
// }
// catch (const std::exception &e)
// {
// try{
// pt = boost::any_cast<Pointf>(value);
// }
// catch (const std::exception &e)
// {
// std::cerr << "Error! Can't cast PointCtrl value" << m_opt_id << "\n";
// return;
// }
// }
set_value(pt);
}

View file

@ -26,6 +26,8 @@ using t_field = std::unique_ptr<Field>;
using t_kill_focus = std::function<void()>;
using t_change = std::function<void(t_config_option_key, boost::any)>;
wxString double_to_string(double const value);
class Field {
protected:
// factory function to defer and enforce creation of derived type.
@ -83,7 +85,7 @@ public:
virtual wxWindow* getWindow() { return nullptr; }
bool is_matched(std::string string, std::string pattern);
boost::any get_value_by_opt_type(wxString str, ConfigOptionType type);
boost::any get_value_by_opt_type(wxString str);
/// Factory method for generating new derived classes.
template<class T>

View file

@ -5,9 +5,9 @@
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/format.hpp>
#if __APPLE__
#import <IOKit/pwr_mgt/IOPMLib.h>
@ -28,7 +28,6 @@
#include <wx/app.h>
#include <wx/button.h>
#include <wx/config.h>
#include <wx/dir.h>
#include <wx/filename.h>
#include <wx/frame.h>
@ -45,6 +44,7 @@
#include "TabIface.hpp"
#include "AppConfig.hpp"
#include "Utils.hpp"
#include "Preferences.hpp"
namespace Slic3r { namespace GUI {
@ -171,6 +171,7 @@ void break_to_debugger()
wxApp *g_wxApp = nullptr;
wxFrame *g_wxMainFrame = nullptr;
wxNotebook *g_wxTabPanel = nullptr;
AppConfig *g_AppConfig = nullptr;
std::vector<Tab *> g_tabs_list;
@ -191,6 +192,11 @@ void set_tab_panel(wxNotebook *tab_panel)
g_wxTabPanel = tab_panel;
}
void set_app_config(AppConfig *app_config)
{
g_AppConfig = app_config;
}
std::vector<Tab *>& get_tabs_list()
{
return g_tabs_list;
@ -215,7 +221,7 @@ bool select_language(wxArrayString & names,
wxArrayLong & identifiers)
{
wxCHECK_MSG(names.Count() == identifiers.Count(), false,
_L("Array of language names and identifiers should have the same size."));
_(L("Array of language names and identifiers should have the same size.")));
int init_selection = 0;
long current_language = g_wxLocale ? g_wxLocale->GetLanguage() : wxLANGUAGE_UNKNOWN;
for (auto lang : identifiers){
@ -226,7 +232,7 @@ bool select_language(wxArrayString & names,
}
if (init_selection == identifiers.size())
init_selection = 0;
long index = wxGetSingleChoiceIndex(_L("Select the language"), _L("Language"),
long index = wxGetSingleChoiceIndex(_(L("Select the language")), _(L("Language")),
names, init_selection);
if (index != -1)
{
@ -241,13 +247,14 @@ bool select_language(wxArrayString & names,
bool load_language()
{
wxConfig config(g_wxApp->GetAppName());
long language;
if (!config.Read(wxT("wxTranslation_Language"),
&language, wxLANGUAGE_UNKNOWN))
{
if (!g_AppConfig->has("translation_language"))
language = wxLANGUAGE_UNKNOWN;
else {
auto str_language = g_AppConfig->get("translation_language");
language = str_language != "" ? stol(str_language) : wxLANGUAGE_UNKNOWN;
}
if (language == wxLANGUAGE_UNKNOWN)
return false;
wxArrayString names;
@ -269,13 +276,13 @@ bool load_language()
void save_language()
{
wxConfig config(g_wxApp->GetAppName());
long language = wxLANGUAGE_UNKNOWN;
if (g_wxLocale) {
language = g_wxLocale->GetLanguage();
}
config.Write(wxT("wxTranslation_Language"), language);
config.Flush();
std::string str_language = std::to_string(language);
g_AppConfig->set("translation_language", str_language);
g_AppConfig->save();
}
void get_installed_languages(wxArrayString & names,
@ -290,15 +297,12 @@ void get_installed_languages(wxArrayString & names,
wxString name = wxLocale::GetLanguageName(wxLANGUAGE_DEFAULT);
if (!name.IsEmpty())
{
names.Add(_L("Default"));
names.Add(_(L("Default")));
identifiers.Add(wxLANGUAGE_DEFAULT);
}
for (bool cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_DIRS);
cont; cont = dir.GetNext(&filename))
{
wxLogTrace(wxTraceMask(),
"L10n: Directory found = \"%s\"",
filename.GetData());
langinfo = wxLocale::FindLanguageInfo(filename);
if (langinfo != NULL)
{
@ -318,33 +322,39 @@ void add_debug_menu(wxMenuBar *menu, int event_language_change)
{
//#if 0
auto local_menu = new wxMenu();
local_menu->Append(wxWindow::NewControlId(1), _L("Change Application Language"));
local_menu->Append(wxWindow::NewControlId(1), _(L("Change Application Language")));
local_menu->Bind(wxEVT_MENU, [event_language_change](wxEvent&){
wxArrayString names;
wxArrayLong identifiers;
get_installed_languages(names, identifiers);
if (select_language(names, identifiers)){
save_language();
show_info(g_wxTabPanel, "Application will be restarted", "Attention!");
show_info(g_wxTabPanel, _(L("Application will be restarted")), _(L("Attention!")));
if (event_language_change > 0) {
wxCommandEvent event(event_language_change);
g_wxApp->ProcessEvent(event);
}
}
});
menu->Append(local_menu, _T("&Localization"));
menu->Append(local_menu, _(L("&Localization")));
//#endif
}
void create_preset_tabs(PresetBundle *preset_bundle, AppConfig *app_config,
void open_preferences_dialog(int event_preferences)
{
auto dlg = new PreferencesDialog(g_wxMainFrame, event_preferences);
dlg->ShowModal();
}
void create_preset_tabs(PresetBundle *preset_bundle,
bool no_controller, bool is_disabled_button_browse, bool is_user_agent,
int event_value_change, int event_presets_changed,
int event_button_browse, int event_button_test)
{
add_created_tab(new TabPrint (g_wxTabPanel, no_controller), preset_bundle, app_config);
add_created_tab(new TabFilament (g_wxTabPanel, no_controller), preset_bundle, app_config);
add_created_tab(new TabPrint (g_wxTabPanel, no_controller), preset_bundle);
add_created_tab(new TabFilament (g_wxTabPanel, no_controller), preset_bundle);
add_created_tab(new TabPrinter (g_wxTabPanel, no_controller, is_disabled_button_browse, is_user_agent),
preset_bundle, app_config);
preset_bundle);
for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) {
Tab *tab = dynamic_cast<Tab*>(g_wxTabPanel->GetPage(i));
if (! tab)
@ -378,8 +388,14 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b
try{
switch (config.def()->get(opt_key)->type){
case coFloatOrPercent:{
const auto &val = *config.option<ConfigOptionFloatOrPercent>(opt_key);
config.set_key_value(opt_key, new ConfigOptionFloatOrPercent(boost::any_cast<double>(value), val.percent));
std::string str = boost::any_cast<std::string>(value);
bool percent = false;
if (str.back() == '%'){
str.pop_back();
percent = true;
}
double val = stod(str);
config.set_key_value(opt_key, new ConfigOptionFloatOrPercent(val, percent));
break;}
case coPercent:
config.set_key_value(opt_key, new ConfigOptionPercent(boost::any_cast<double>(value)));
@ -389,11 +405,15 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b
val = boost::any_cast<double>(value);
break;
}
case coPercents:
case coFloats:{
double& val = config.opt_float(opt_key, 0);
val = boost::any_cast<double>(value);
case coPercents:{
ConfigOptionPercents* vec_new = new ConfigOptionPercents{ boost::any_cast<double>(value) };
config.option<ConfigOptionPercents>(opt_key)->set_at(vec_new, opt_index, opt_index);
break;
}
case coFloats:{
ConfigOptionFloats* vec_new = new ConfigOptionFloats{ boost::any_cast<double>(value) };
config.option<ConfigOptionFloats>(opt_key)->set_at(vec_new, opt_index, opt_index);
break;
}
case coString:
config.set_key_value(opt_key, new ConfigOptionString(boost::any_cast<std::string>(value)));
@ -406,7 +426,7 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b
}
else{
ConfigOptionStrings* vec_new = new ConfigOptionStrings{ boost::any_cast<std::string>(value) };
config.option<ConfigOptionStrings>(opt_key)->set_at(vec_new, opt_index, opt_index);
config.option<ConfigOptionStrings>(opt_key)->set_at(vec_new, opt_index, 0);
}
}
break;
@ -415,14 +435,14 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b
break;
case coBools:{
ConfigOptionBools* vec_new = new ConfigOptionBools{ boost::any_cast<bool>(value) };
config.option<ConfigOptionBools>(opt_key)->set_at(vec_new, opt_index, opt_index);
config.option<ConfigOptionBools>(opt_key)->set_at(vec_new, opt_index, 0);
break;}
case coInt:
config.set_key_value(opt_key, new ConfigOptionInt(boost::any_cast<int>(value)));
break;
case coInts:{
ConfigOptionInts* vec_new = new ConfigOptionInts{ boost::any_cast<int>(value) };
config.option<ConfigOptionInts>(opt_key)->set_at(vec_new, opt_index, opt_index);
config.option<ConfigOptionInts>(opt_key)->set_at(vec_new, opt_index, 0);
}
break;
case coEnum:{
@ -455,9 +475,8 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b
}
}
void add_created_tab(Tab* panel, PresetBundle *preset_bundle, AppConfig *app_config)
void add_created_tab(Tab* panel, PresetBundle *preset_bundle)
{
panel->m_show_btn_incompatible_presets = app_config->get("show_incompatible_presets").empty();
panel->create_preset_tab(preset_bundle);
// Load the currently selected preset into the GUI, update the preset selection box.
@ -466,15 +485,22 @@ void add_created_tab(Tab* panel, PresetBundle *preset_bundle, AppConfig *app_con
}
void show_error(wxWindow* parent, wxString message){
auto msg_wingow = new wxMessageDialog(parent, message, _L("Error"), wxOK | wxICON_ERROR);
auto msg_wingow = new wxMessageDialog(parent, message, _(L("Error")), wxOK | wxICON_ERROR);
msg_wingow->ShowModal();
}
void show_info(wxWindow* parent, wxString message, wxString title){
auto msg_wingow = new wxMessageDialog(parent, message, title.empty() ? _L("Notice") : title, wxOK | wxICON_INFORMATION);
auto msg_wingow = new wxMessageDialog(parent, message, title.empty() ? _(L("Notice")) : title, wxOK | wxICON_INFORMATION);
msg_wingow->ShowModal();
}
void warning_catcher(wxWindow* parent, wxString message){
if (message == _(L("GLUquadricObjPtr | Attempt to free unreferenced scalar")) )
return;
auto msg = new wxMessageDialog(parent, message, _(L("Warning")), wxOK | wxICON_WARNING);
msg->ShowModal();
}
wxApp* get_app(){
return g_wxApp;
}
@ -487,17 +513,24 @@ void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string
wxCheckListBoxComboPopup* popup = new wxCheckListBoxComboPopup;
if (popup != nullptr)
{
// FIXME If the following line is removed, the combo box popup list will not react to mouse clicks.
// On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10.
comboCtrl->UseAltPopupWindow();
comboCtrl->EnablePopupAnimation(false);
comboCtrl->SetPopupControl(popup);
popup->SetStringValue(text);
popup->Connect(wxID_ANY, wxEVT_CHECKLISTBOX, wxCommandEventHandler(wxCheckListBoxComboPopup::OnCheckListBox), nullptr, popup);
popup->Connect(wxID_ANY, wxEVT_LISTBOX, wxCommandEventHandler(wxCheckListBoxComboPopup::OnListBoxSelection), nullptr, popup);
popup->SetStringValue(from_u8(text));
popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); });
popup->Bind(wxEVT_LISTBOX, [popup](wxCommandEvent& evt) { popup->OnListBoxSelection(evt); });
popup->Bind(wxEVT_KEY_DOWN, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); });
popup->Bind(wxEVT_KEY_UP, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); });
std::vector<std::string> items_str;
boost::split(items_str, items, boost::is_any_of("|"), boost::token_compress_off);
for (const std::string& item : items_str)
{
popup->Append(item);
popup->Append(from_u8(item));
}
for (unsigned int i = 0; i < popup->GetCount(); ++i)
@ -524,4 +557,34 @@ int combochecklist_get_flags(wxComboCtrl* comboCtrl)
return flags;
}
AppConfig* get_app_config()
{
return g_AppConfig;
}
wxString L_str(std::string str)
{
//! Explicitly specify that the source string is already in UTF-8 encoding
return wxGetTranslation(wxString(str.c_str(), wxConvUTF8));
}
wxString from_u8(std::string str)
{
return wxString::FromUTF8(str.c_str());
}
wxWindow *get_widget_by_id(int id)
{
if (g_wxMainFrame == nullptr) {
throw std::runtime_error("Main frame not set");
}
wxWindow *window = g_wxMainFrame->FindWindow(id);
if (window == nullptr) {
throw std::runtime_error((boost::format("Could not find widget by ID: %1%") % id).str());
}
return window;
}
} }

View file

@ -6,6 +6,7 @@
#include "Config.hpp"
class wxApp;
class wxWindow;
class wxFrame;
class wxWindow;
class wxMenuBar;
@ -23,10 +24,19 @@ class AppConfig;
class DynamicPrintConfig;
class TabIface;
//! macro used to localization, return wxString
#define _L(s) wxGetTranslation(s)
//! macro used to localization, return const CharType *
#define _LU8(s) wxGetTranslation(s).ToUTF8().data()
// !!! If you needed to translate some wxString,
// !!! please use _(L(string))
// !!! _() - is a standard wxWidgets macro to translate
// !!! L() is used only for marking localizable string
// !!! It will be used in "xgettext" to create a Locating Message Catalog.
#define L(s) s
//! macro used to localization, return wxScopedCharBuffer
//! With wxConvUTF8 explicitly specify that the source string is already in UTF-8 encoding
#define _CHB(s) wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str()
// Minimal buffer length for translated string (char buf[MIN_BUF_LENGTH_FOR_L])
#define MIN_BUF_LENGTH_FOR_L 128
namespace GUI {
@ -39,8 +49,9 @@ inline t_file_wild_card& get_file_wild_card() {
FILE_WILDCARDS["known"] = "Known files (*.stl, *.obj, *.amf, *.xml, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.prusa;*.PRUSA";
FILE_WILDCARDS["stl"] = "STL files (*.stl)|*.stl;*.STL";
FILE_WILDCARDS["obj"] = "OBJ files (*.obj)|*.obj;*.OBJ";
FILE_WILDCARDS["amf"] = "AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML";
FILE_WILDCARDS["prusa"] = "Prusa Control files (*.prusa)|*.prusa;*.PRUSA";
FILE_WILDCARDS["amf"] = "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML";
FILE_WILDCARDS["3mf"] = "3MF files (*.3mf)|*.3mf;*.3MF;";
FILE_WILDCARDS["prusa"] = "Prusa Control files (*.prusa)|*.prusa;*.PRUSA";
FILE_WILDCARDS["ini"] = "INI files *.ini|*.ini;*.INI";
FILE_WILDCARDS["gcode"] = "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC";
FILE_WILDCARDS["svg"] = "SVG files *.svg|*.svg;*.SVG";
@ -58,22 +69,31 @@ void break_to_debugger();
void set_wxapp(wxApp *app);
void set_main_frame(wxFrame *main_frame);
void set_tab_panel(wxNotebook *tab_panel);
void set_app_config(AppConfig *app_config);
AppConfig* get_app_config();
wxApp* get_app();
void add_debug_menu(wxMenuBar *menu, int event_language_change);
// Create "Preferences" dialog after selecting menu "Preferences" in Perl part
void open_preferences_dialog(int event_preferences);
// Create a new preset tab (print, filament and printer),
void create_preset_tabs(PresetBundle *preset_bundle, AppConfig *app_config,
void create_preset_tabs(PresetBundle *preset_bundle,
bool no_controller, bool is_disabled_button_browse, bool is_user_agent,
int event_value_change, int event_presets_changed,
int event_button_browse, int event_button_test);
TabIface* get_preset_tab_iface(char *name);
// add it at the end of the tab panel.
void add_created_tab(Tab* panel, PresetBundle *preset_bundle, AppConfig *app_config);
void add_created_tab(Tab* panel, PresetBundle *preset_bundle);
// Change option value in config
void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, boost::any value, int opt_index = 0);
void show_error(wxWindow* parent, wxString message);
void show_info(wxWindow* parent, wxString message, wxString title);
void warning_catcher(wxWindow* parent, wxString message);
// load language saved at application config
bool load_language();
@ -97,6 +117,13 @@ void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string
// encoded inside an int.
int combochecklist_get_flags(wxComboCtrl* comboCtrl);
// Return translated std::string as a wxString
wxString L_str(std::string str);
// Return wxString from std::string in UTF8
wxString from_u8(std::string str);
wxWindow *get_widget_by_id(int id);
}
}

View file

@ -2,7 +2,6 @@
#include "ConfigExceptions.hpp"
#include <utility>
#include <wx/tooltip.h>
#include <wx/numformatter.h>
namespace Slic3r { namespace GUI {
@ -122,13 +121,13 @@ void OptionsGroup::append_line(const Line& line) {
}
// If there's a widget, build it and add the result to the sizer.
if (line.widget != nullptr) {
auto wgt = line.widget(parent());
if (line.widget != nullptr) {
auto wgt = line.widget(parent());
grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, wxOSX ? 0 : 5);
return;
}
// if we have a single option with no sidetext just add it directly to the grid sizer
return;
}
// if we have a single option with no sidetext just add it directly to the grid sizer
if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 &&
option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) {
const auto& option = option_set.front();
@ -152,7 +151,12 @@ void OptionsGroup::append_line(const Line& line) {
ConfigOptionDef option = opt.opt;
// add label if any
if (option.label != "") {
auto field_label = new wxStaticText(parent(), wxID_ANY, wxString::FromUTF8(option.label.c_str()) + ":", wxDefaultPosition, wxDefaultSize);
wxString str_label = L_str(option.label);
//! To correct translation by context have to use wxGETTEXT_IN_CONTEXT macro from wxWidget 3.1.1
// wxString str_label = (option.label == "Top" || option.label == "Bottom") ?
// wxGETTEXT_IN_CONTEXT("Layers", wxString(option.label.c_str()):
// L_str(option.label);
auto field_label = new wxStaticText(parent(), wxID_ANY, str_label + ":", wxDefaultPosition, wxDefaultSize);
field_label->SetFont(label_font);
sizer->Add(field_label, 0, wxALIGN_CENTER_VERTICAL, 0);
}
@ -166,7 +170,7 @@ void OptionsGroup::append_line(const Line& line) {
// add sidetext if any
if (option.sidetext != "") {
auto sidetext = new wxStaticText(parent(), wxID_ANY, wxString::FromUTF8(option.sidetext.c_str()), wxDefaultPosition, wxDefaultSize);
auto sidetext = new wxStaticText(parent(), wxID_ANY, L_str(option.sidetext), wxDefaultPosition, wxDefaultSize);
sidetext->SetFont(sidetext_font);
sizer->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4);
}
@ -188,7 +192,7 @@ void OptionsGroup::append_line(const Line& line) {
}
Line OptionsGroup::create_single_option_line(const Option& option) const {
Line retval{ wxString::FromUTF8(option.opt.label.c_str()), wxString::FromUTF8(option.opt.tooltip.c_str()) };
Line retval{ L_str(option.opt.label), L_str(option.opt.tooltip) };
Option tmp(option);
tmp.opt.label = std::string("");
retval.append_option(tmp);
@ -203,7 +207,7 @@ void OptionsGroup::on_change_OG(t_config_option_key id, /*config_value*/boost::a
Option ConfigOptionsGroup::get_option(const std::string opt_key, int opt_index /*= -1*/)
{
if (!m_config->has(opt_key)) {
//! exception ("No $opt_key in ConfigOptionsGroup config");
std::cerr << "No " << opt_key << " in ConfigOptionsGroup config.";
}
std::string opt_id = opt_index == -1 ? opt_key : opt_key + "#" + std::to_string(opt_index);
@ -287,14 +291,6 @@ boost::any ConfigOptionsGroup::config_value(std::string opt_key, int opt_index,
}
}
wxString double_to_string(double const value)
{
int precision = 10 * value - int(10 * value) == 0 ? 1 : 2;
return value - int(value) == 0 ?
wxString::Format(_T("%i"), int(value)) :
wxNumberFormatter::ToString(value, precision, wxNumberFormatter::Style_None);
}
boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std::string opt_key, int opt_index/* = -1*/)
{
size_t idx = opt_index == -1 ? 0 : opt_index;
@ -325,9 +321,9 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std:
case coFloats:
case coFloat:{
double val = opt->type == coFloats ?
config.opt_float(opt_key, idx/*0opt_index*/) :
config.opt_float(opt_key, idx) :
opt->type == coFloat ? config.opt_float(opt_key) :
config.option<ConfigOptionPercents>(opt_key)->values.at(idx/*0*/);
config.option<ConfigOptionPercents>(opt_key)->values.at(idx);
ret = double_to_string(val);
}
break;
@ -338,19 +334,19 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std:
if (config.option<ConfigOptionStrings>(opt_key)->values.empty())
ret = text_value;
else
ret = static_cast<wxString>(config.opt_string(opt_key, static_cast<unsigned int>(idx/*0*/)/*opt_index*/));
ret = static_cast<wxString>(config.opt_string(opt_key, static_cast<unsigned int>(idx)));
break;
case coBool:
ret = config.opt_bool(opt_key);
break;
case coBools:
ret = config.opt_bool(opt_key, idx/*0opt_index*/);
ret = config.opt_bool(opt_key, idx);
break;
case coInt:
ret = config.opt_int(opt_key);
break;
case coInts:
ret = config.opt_int(opt_key, idx/*0/*opt_index*/);
ret = config.opt_int(opt_key, idx);
break;
case coEnum:{
if (opt_key.compare("external_fill_pattern") == 0 ||
@ -369,7 +365,7 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std:
break;
case coPoints:{
const auto &value = *config.option<ConfigOptionPoints>(opt_key);
ret = value.values.at(idx/*0*/);
ret = value.values.at(idx);
}
break;
case coNone:
@ -397,14 +393,5 @@ void ogStaticText::SetText(wxString value)
GetParent()->Layout();
}
void Option::translate()
{
opt.label = _LU8(opt.label);
opt.tooltip = _LU8(opt.tooltip);
opt.sidetext = _LU8(opt.sidetext);
opt.full_label = _LU8(opt.full_label);
opt.category = _LU8(opt.category);
}
} // GUI
} // Slic3r

View file

@ -35,8 +35,7 @@ struct Option {
bool readonly {false};
Option(const ConfigOptionDef& _opt, t_config_option_key id) :
opt(_opt), opt_id(id) { translate(); }
void translate();
opt(_opt), opt_id(id) {}
};
using t_option = std::unique_ptr<Option>; //!
@ -90,9 +89,22 @@ public:
void append_single_option_line(const Option& option) { append_line(create_single_option_line(option)); }
// return a non-owning pointer reference
inline /*const*/ Field* get_field(t_config_option_key id) const { try { return m_fields.at(id).get(); } catch (std::out_of_range e) { return nullptr; } }
bool set_value(t_config_option_key id, boost::any value) { try { m_fields.at(id)->set_value(value); return true; } catch (std::out_of_range e) { return false; } }
boost::any get_value(t_config_option_key id) { boost::any out; try { out = m_fields.at(id)->get_value(); } catch (std::out_of_range e) { ; } return out; }
inline Field* get_field(t_config_option_key id) const{
if (m_fields.find(id) == m_fields.end()) return nullptr;
return m_fields.at(id).get();
}
bool set_value(t_config_option_key id, boost::any value) {
if (m_fields.find(id) == m_fields.end()) return false;
m_fields.at(id)->set_value(value);
return true;
}
boost::any get_value(t_config_option_key id) {
boost::any out;
if (m_fields.find(id) == m_fields.end()) ;
else
out = m_fields.at(id)->get_value();
return out;
}
inline void enable() { for (auto& field : m_fields) field.second->enable(); }
inline void disable() { for (auto& field : m_fields) field.second->disable(); }

View file

@ -0,0 +1,120 @@
#include "Preferences.hpp"
#include "AppConfig.hpp"
#include "OptionsGroup.hpp"
namespace Slic3r {
namespace GUI {
void PreferencesDialog::build()
{
auto app_config = get_app_config();
m_optgroup = std::make_shared<ConfigOptionsGroup>(this, _(L("General")));
m_optgroup->label_width = 200;
m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
};
// $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
// opt_id = > 'version_check',
// type = > 'bool',
// label = > 'Check for updates',
// tooltip = > 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.',
// default = > $app_config->get("version_check") // 1,
// readonly = > !wxTheApp->have_version_check,
// ));
ConfigOptionDef def;
def.label = L("Remember output directory");
def.type = coBool;
def.tooltip = L("If this is enabled, Slic3r will prompt the last output directory "
"instead of the one containing the input files.");
def.default_value = new ConfigOptionBool{ app_config->get("remember_output_path")[0] == '1' }; // 1;
Option option(def, "remember_output_path");
m_optgroup->append_single_option_line(option);
def.label = L("Auto-center parts");
def.type = coBool;
def.tooltip = L("If this is enabled, Slic3r will auto-center objects "
"around the print bed center.");
def.default_value = new ConfigOptionBool{ app_config->get("autocenter")[0] == '1' }; // 1;
option = Option (def,"autocenter");
m_optgroup->append_single_option_line(option);
def.label = L("Background processing");
def.type = coBool;
def.tooltip = L("If this is enabled, Slic3r will pre-process objects as soon "
"as they\'re loaded in order to save time when exporting G-code.");
def.default_value = new ConfigOptionBool{ app_config->get("background_processing")[0] == '1' }; // 1;
option = Option (def,"background_processing");
m_optgroup->append_single_option_line(option);
def.label = L("Disable USB/serial connection");
def.type = coBool;
def.tooltip = L("Disable communication with the printer over a serial / USB cable. "
"This simplifies the user interface in case the printer is never attached to the computer.");
def.default_value = new ConfigOptionBool{ app_config->get("no_controller")[0] == '1' }; // 1;
option = Option (def,"no_controller");
m_optgroup->append_single_option_line(option);
def.label = L("Suppress \" - default - \" presets");
def.type = coBool;
def.tooltip = L("Suppress \" - default - \" presets in the Print / Filament / Printer "
"selections once there are any other valid presets available.");
def.default_value = new ConfigOptionBool{ app_config->get("no_defaults")[0] == '1' }; // 1;
option = Option (def,"no_defaults");
m_optgroup->append_single_option_line(option);
def.label = L("Show incompatible print and filament presets");
def.type = coBool;
def.tooltip = L("When checked, the print and filament presets are shown in the preset editor "
"even if they are marked as incompatible with the active printer");
def.default_value = new ConfigOptionBool{ app_config->get("show_incompatible_presets")[0] == '1' }; // 1;
option = Option (def,"show_incompatible_presets");
m_optgroup->append_single_option_line(option);
def.label = L("Use legacy OpenGL 1.1 rendering");
def.type = coBool;
def.tooltip = L("If you have rendering issues caused by a buggy OpenGL 2.0 driver, "
"you may try to check this checkbox. This will disable the layer height "
"editing and anti aliasing, so it is likely better to upgrade your graphics driver.");
def.default_value = new ConfigOptionBool{ app_config->get("use_legacy_opengl")[0] == '1' }; // 1;
option = Option (def,"use_legacy_opengl");
m_optgroup->append_single_option_line(option);
auto sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(m_optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); });
sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
SetSizer(sizer);
sizer->SetSizeHints(this);
}
void PreferencesDialog::accept()
{
if (m_values.find("no_controller") != m_values.end()||
m_values.find("no_defaults") != m_values.end()||
m_values.find("use_legacy_opengl")!= m_values.end()) {
warning_catcher(this, _(L("You need to restart Slic3r to make the changes effective.")));
}
auto app_config = get_app_config();
for (std::map<std::string, std::string>::iterator it = m_values.begin(); it != m_values.end(); ++it) {
app_config->set(it->first, it->second);
}
EndModal(wxID_OK);
Close(); // needed on Linux
// Nothify the UI to update itself from the ini file.
if (m_event_preferences > 0) {
wxCommandEvent event(m_event_preferences);
get_app()->ProcessEvent(event);
}
}
} // GUI
} // Slic3r

View file

@ -0,0 +1,27 @@
#include "GUI.hpp"
#include <wx/dialog.h>
#include <map>
namespace Slic3r {
namespace GUI {
class ConfigOptionsGroup;
class PreferencesDialog : public wxDialog
{
std::map<std::string, std::string> m_values;
std::shared_ptr<ConfigOptionsGroup> m_optgroup;
int m_event_preferences;
public:
PreferencesDialog(wxWindow* parent, int event_preferences) : wxDialog(parent, wxID_ANY, _(L("Preferences")),
wxDefaultPosition, wxDefaultSize), m_event_preferences(event_preferences) { build(); }
~PreferencesDialog(){ }
void build();
void accept();
};
} // GUI
} // Slic3r

View file

@ -224,7 +224,7 @@ const std::vector<std::string>& Preset::printer_options()
if (s_opts.empty()) {
s_opts = {
"bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed",
"octoprint_host", "octoprint_apikey", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
"octoprint_host", "octoprint_apikey", "octoprint_cafile", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
"between_objects_gcode", "printer_notes", "cooling_tube_retraction", "cooling_tube_length", "parking_pos_retraction"
};

View file

@ -14,6 +14,7 @@
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/locale.hpp>
#include <boost/log/trivial.hpp>
#include <wx/dcmemory.h>
#include <wx/image.h>
@ -474,6 +475,125 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const
this->update_compatible_with_printer(false);
}
// Process the Config Bundle loaded as a Boost property tree.
// For each print, filament and printer preset (group defined by group_name), apply the inherited presets.
// The presets starting with '*' are considered non-terminal and they are
// removed through the flattening process by this function.
// This function will never fail, but it will produce error messages through boost::log.
static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree, const std::string &group_name)
{
namespace pt = boost::property_tree;
typedef std::pair<pt::ptree::key_type, pt::ptree> ptree_child_type;
// 1) For the group given by group_name, initialize the presets.
struct Prst {
Prst(const std::string &name, pt::ptree *node) : name(name), node(node) {}
// Name of this preset. If the name starts with '*', it is an intermediate preset,
// which will not make it into the result.
const std::string name;
// Link to the source boost property tree node, owned by tree.
pt::ptree *node;
// Link to the presets, from which this preset inherits.
std::vector<Prst*> inherits;
// Link to the presets, for which this preset is a direct parent.
std::vector<Prst*> parent_of;
// When running the Kahn's Topological sorting algorithm, this counter is decreased from inherits.size() to zero.
// A cycle is indicated, if the number does not drop to zero after the Kahn's algorithm finishes.
size_t num_incoming_edges_left = 0;
// Sorting by the name, to be used when inserted into std::set.
bool operator==(const Prst &rhs) const { return this->name == rhs.name; }
bool operator< (const Prst &rhs) const { return this->name < rhs.name; }
};
// Find the presets, store them into a std::map, addressed by their names.
std::set<Prst> presets;
std::string group_name_preset = group_name + ":";
for (auto &section : tree)
if (boost::starts_with(section.first, group_name_preset) && section.first.size() > group_name_preset.size())
presets.emplace(section.first.substr(group_name_preset.size()), &section.second);
// Fill in the "inherits" and "parent_of" members, report invalid inheritance fields.
for (const Prst &prst : presets) {
// Parse the list of comma separated values, possibly enclosed in quotes.
std::vector<std::string> inherits_names;
if (Slic3r::unescape_strings_cstyle(prst.node->get<std::string>("inherits", ""), inherits_names)) {
// Resolve the inheritance by name.
std::vector<Prst*> &inherits_nodes = const_cast<Prst&>(prst).inherits;
for (const std::string &node_name : inherits_names) {
auto it = presets.find(Prst(node_name, nullptr));
if (it == presets.end())
BOOST_LOG_TRIVIAL(error) << "flatten_configbundle_hierarchy: The preset " << prst.name << " inherits an unknown preset \"" << node_name << "\"";
else {
inherits_nodes.emplace_back(const_cast<Prst*>(&(*it)));
inherits_nodes.back()->parent_of.emplace_back(const_cast<Prst*>(&prst));
}
}
} else {
BOOST_LOG_TRIVIAL(error) << "flatten_configbundle_hierarchy: The preset " << prst.name << " has an invalid \"inherits\" field";
}
// Remove the "inherits" key, it has no meaning outside the config bundle.
const_cast<pt::ptree*>(prst.node)->erase("inherits");
}
// 2) Create a linear ordering for the directed acyclic graph of preset inheritance.
// https://en.wikipedia.org/wiki/Topological_sorting
// Kahn's algorithm.
std::vector<Prst*> sorted;
{
// Initialize S with the set of all nodes with no incoming edge.
std::deque<Prst*> S;
for (const Prst &prst : presets)
if (prst.inherits.empty())
S.emplace_back(const_cast<Prst*>(&prst));
else
const_cast<Prst*>(&prst)->num_incoming_edges_left = prst.inherits.size();
while (! S.empty()) {
Prst *n = S.front();
S.pop_front();
sorted.emplace_back(n);
for (Prst *m : n->parent_of) {
assert(m->num_incoming_edges_left > 0);
if (-- m->num_incoming_edges_left == 0) {
// We have visited all parents of m.
S.emplace_back(m);
}
}
}
if (sorted.size() < presets.size()) {
for (const Prst &prst : presets)
if (prst.num_incoming_edges_left)
BOOST_LOG_TRIVIAL(error) << "flatten_configbundle_hierarchy: The preset " << prst.name << " has cyclic dependencies";
}
}
// Apply the dependencies in their topological ordering.
for (Prst *prst : sorted) {
// Merge the preset nodes in their order of application.
// 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())
prst->node->add_child(it->first, it->second);
}
// Remove the "internal" presets from the ptree. These presets are marked with '*'.
group_name_preset += '*';
for (auto it_section = tree.begin(); it_section != tree.end(); ) {
if (boost::starts_with(it_section->first, group_name_preset) && it_section->first.size() > group_name_preset.size())
// Remove the "internal" preset from the ptree.
it_section = tree.erase(it_section);
else
// Keep the preset.
++ it_section;
}
}
static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree)
{
flatten_configbundle_hierarchy(tree, "print");
flatten_configbundle_hierarchy(tree, "filament");
flatten_configbundle_hierarchy(tree, "printer");
}
// Load a config bundle file, into presets and store the loaded presets into separate files
// of the local configuration directory.
size_t PresetBundle::load_configbundle(const std::string &path, unsigned int flags)
@ -486,6 +606,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
pt::ptree tree;
boost::nowide::ifstream ifs(path);
pt::read_ini(ifs, tree);
// Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed.
flatten_configbundle_hierarchy(tree);
// 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files.
std::vector<std::string> loaded_prints;

View file

@ -6,8 +6,10 @@
#include "Flow.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <wx/intl.h>
#include "../../libslic3r/libslic3r.h"
#include "GUI.hpp"
namespace Slic3r {
@ -21,31 +23,31 @@ std::string PresetHints::cooling_description(const Preset &preset)
int max_fan_speed = preset.config.opt_int("max_fan_speed", 0);
int min_print_speed = int(preset.config.opt_float("min_print_speed", 0) + 0.5);
int fan_below_layer_time = preset.config.opt_int("fan_below_layer_time", 0);
sprintf(buf, "If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).",
sprintf(buf, _CHB(L("If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).")),
slowdown_below_layer_time, max_fan_speed, slowdown_below_layer_time, min_print_speed);
out += buf;
if (fan_below_layer_time > slowdown_below_layer_time) {
sprintf(buf, "\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.",
sprintf(buf, _CHB(L("\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.")),
fan_below_layer_time, max_fan_speed, min_fan_speed);
out += buf;
}
out += "\nDuring the other layers, fan ";
out += _CHB(L("\nDuring the other layers, fan "));
} else {
out = "Fan ";
out = _CHB(L("Fan "));
}
if (preset.config.opt_bool("fan_always_on", 0)) {
int disable_fan_first_layers = preset.config.opt_int("disable_fan_first_layers", 0);
int min_fan_speed = preset.config.opt_int("min_fan_speed", 0);
sprintf(buf, "will always run at %d%% ", min_fan_speed);
sprintf(buf, _CHB(L("will always run at %d%% ")), min_fan_speed);
out += buf;
if (disable_fan_first_layers > 1) {
sprintf(buf, "except for the first %d layers", disable_fan_first_layers);
sprintf(buf, _CHB(L("except for the first %d layers")), disable_fan_first_layers);
out += buf;
}
else if (disable_fan_first_layers == 1)
out += "except for the first layer";
out += _CHB(L("except for the first layer"));
} else
out += "will be turned off.";
out += _CHB(L("will be turned off."));
return out;
}
@ -146,7 +148,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
limit_by_first_layer_speed(std::max(external_perimeter_speed, small_perimeter_speed), max_print_speed));
if (max_flow < external_perimeter_rate) {
max_flow = external_perimeter_rate;
max_flow_extrusion_type = "external perimeters";
max_flow_extrusion_type = _CHB(L("external perimeters"));
}
double perimeter_rate = Flow::new_from_config_width(frPerimeter,
first_positive(first_layer_extrusion_width_ptr, perimeter_extrusion_width, extrusion_width),
@ -155,7 +157,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
limit_by_first_layer_speed(std::max(perimeter_speed, small_perimeter_speed), max_print_speed));
if (max_flow < perimeter_rate) {
max_flow = perimeter_rate;
max_flow_extrusion_type = "perimeters";
max_flow_extrusion_type = _CHB(L("perimeters"));
}
}
if (! bridging && infill_extruder_active) {
@ -164,7 +166,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(infill_speed, max_print_speed);
if (max_flow < infill_rate) {
max_flow = infill_rate;
max_flow_extrusion_type = "infill";
max_flow_extrusion_type = _CHB(L("infill"));
}
}
if (solid_infill_extruder_active) {
@ -174,7 +176,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
(bridging ? bridge_speed : limit_by_first_layer_speed(solid_infill_speed, max_print_speed));
if (max_flow < solid_infill_rate) {
max_flow = solid_infill_rate;
max_flow_extrusion_type = "solid infill";
max_flow_extrusion_type = _CHB(L("solid infill"));
}
if (! bridging) {
double top_solid_infill_rate = Flow::new_from_config_width(frInfill,
@ -182,7 +184,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(top_solid_infill_speed, max_print_speed);
if (max_flow < top_solid_infill_rate) {
max_flow = top_solid_infill_rate;
max_flow_extrusion_type = "top solid infill";
max_flow_extrusion_type = _CHB(L("top solid infill"));
}
}
}
@ -193,7 +195,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
(bridging ? bridge_speed : limit_by_first_layer_speed(support_material_speed, max_print_speed));
if (max_flow < support_material_rate) {
max_flow = support_material_rate;
max_flow_extrusion_type = "support";
max_flow_extrusion_type = _CHB(L("support"));
}
}
if (support_material_interface_extruder_active) {
@ -203,25 +205,25 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
(bridging ? bridge_speed : limit_by_first_layer_speed(support_material_interface_speed, max_print_speed));
if (max_flow < support_material_interface_rate) {
max_flow = support_material_interface_rate;
max_flow_extrusion_type = "support interface";
max_flow_extrusion_type = _CHB(L("support interface"));
}
}
//FIXME handle gap_fill_speed
if (! out.empty())
out += "\n";
out += (first_layer ? "First layer volumetric" : (bridging ? "Bridging volumetric" : "Volumetric"));
out += " flow rate is maximized ";
out += (first_layer ? _CHB(L("First layer volumetric")) : (bridging ? _CHB(L("Bridging volumetric")) : _CHB(L("Volumetric"))));
out += _CHB(L(" flow rate is maximized "));
bool limited_by_max_volumetric_speed = max_volumetric_speed > 0 && max_volumetric_speed < max_flow;
out += (limited_by_max_volumetric_speed ?
"by the print profile maximum" :
("when printing " + max_flow_extrusion_type))
+ " with a volumetric rate ";
_CHB(L("by the print profile maximum")) :
(_CHB(L("when printing ")) + max_flow_extrusion_type))
+ _CHB(L(" with a volumetric rate "));
if (limited_by_max_volumetric_speed)
max_flow = max_volumetric_speed;
char buf[2048];
sprintf(buf, "%3.2f mm³/s", max_flow);
sprintf(buf, _CHB(L("%3.2f mm³/s")), max_flow);
out += buf;
sprintf(buf, " at filament speed %3.2f mm/s.", max_flow / filament_crossection);
sprintf(buf, _CHB(L(" at filament speed %3.2f mm/s.")), max_flow / filament_crossection);
out += buf;
}
@ -238,8 +240,11 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &pre
bool thin_walls = print_config.opt_bool("thin_walls");
float nozzle_diameter = float(printer_config.opt_float("nozzle_diameter", 0));
if (layer_height <= 0.f)
return "Recommended object thin wall thickness: Not available due to invalid layer height.";
std::string out;
if (layer_height <= 0.f){
out += _CHB(L("Recommended object thin wall thickness: Not available due to invalid layer height."));
return out;
}
Flow external_perimeter_flow = Flow::new_from_config_width(
frExternalPerimeter,
@ -250,18 +255,18 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &pre
*print_config.opt<ConfigOptionFloatOrPercent>("perimeter_extrusion_width"),
nozzle_diameter, layer_height, false);
std::string out;
if (num_perimeters > 0) {
int num_lines = std::min(num_perimeters * 2, 10);
char buf[256];
sprintf(buf, "Recommended object thin wall thickness for layer height %.2f and ", layer_height);
sprintf(buf, _CHB(L("Recommended object thin wall thickness for layer height %.2f and ")), layer_height);
out += buf;
// Start with the width of two closely spaced
double width = external_perimeter_flow.width + external_perimeter_flow.spacing();
for (int i = 2; i <= num_lines; thin_walls ? ++ i : i += 2) {
if (i > 2)
out += ", ";
sprintf(buf, "%d lines: %.2lf mm", i, width);
sprintf(buf, _CHB(L("%d lines: %.2lf mm")), i, width);
out += buf;
width += perimeter_flow.spacing() * (thin_walls ? 1.f : 2.f);
}

File diff suppressed because it is too large Load diff

View file

@ -107,7 +107,7 @@ protected:
public:
PresetBundle* m_preset_bundle;
bool m_show_btn_incompatible_presets;
bool m_show_btn_incompatible_presets = false;
PresetCollection* m_presets;
DynamicPrintConfig* m_config;
@ -180,7 +180,7 @@ class TabPrint : public Tab
public:
TabPrint() {}
TabPrint(wxNotebook* parent, bool no_controller) :
Tab(parent, _L("Print Settings"), "print", no_controller) {}
Tab(parent, _(L("Print Settings")), "print", no_controller) {}
~TabPrint(){}
ogStaticText* m_recommended_thin_wall_thickness_description_line;
@ -200,7 +200,7 @@ class TabFilament : public Tab
public:
TabFilament() {}
TabFilament(wxNotebook* parent, bool no_controller) :
Tab(parent, _L("Filament Settings"), "filament", no_controller) {}
Tab(parent, _(L("Filament Settings")), "filament", no_controller) {}
~TabFilament(){}
void build() override;
@ -226,7 +226,7 @@ public:
TabPrinter() {}
TabPrinter(wxNotebook* parent, bool no_controller, bool is_disabled_btn_browse, bool is_user_agent) :
Tab(parent, _L("Printer Settings"), "printer", no_controller),
Tab(parent, _(L("Printer Settings")), "printer", no_controller),
m_is_disabled_button_browse(is_disabled_btn_browse),
m_is_user_agent(is_user_agent) {}
~TabPrinter(){}
@ -246,7 +246,7 @@ public:
class SavePresetWindow :public wxDialog
{
public:
SavePresetWindow(wxWindow* parent) :wxDialog(parent, wxID_ANY, _L("Save preset")){}
SavePresetWindow(wxWindow* parent) :wxDialog(parent, wxID_ANY, _(L("Save preset"))){}
~SavePresetWindow(){}
std::string m_chosen_name;

View file

@ -11,7 +11,7 @@ void TabIface::load_config(DynamicPrintConfig* config) { m_tab->load_config(*con
void TabIface::load_key_value(char* opt_key, char* value){ m_tab->load_key_value(opt_key, static_cast<std::string>(value)); }
bool TabIface::current_preset_is_dirty() { return m_tab->current_preset_is_dirty();}
void TabIface::OnActivate() { return m_tab->OnActivate();}
std::string TabIface::title() { return m_tab->title().ToStdString();}
std::string TabIface::title() { return m_tab->title().ToUTF8().data(); }
DynamicPrintConfig* TabIface::get_config() { return m_tab->get_config(); }
PresetCollection* TabIface::get_presets() { return m_tab!=nullptr ? m_tab->get_presets() : nullptr; }
std::vector<std::string> TabIface::get_dependent_tabs() { return m_tab->get_dependent_tabs(); }

View file

@ -1,6 +1,8 @@
#include "wxExtensions.hpp"
const unsigned int wxCheckListBoxComboPopup::Height = 210;
const unsigned int wxCheckListBoxComboPopup::DefaultWidth = 200;
const unsigned int wxCheckListBoxComboPopup::DefaultHeight = 200;
const unsigned int wxCheckListBoxComboPopup::DefaultItemHeight = 18;
bool wxCheckListBoxComboPopup::Create(wxWindow* parent)
{
@ -25,16 +27,55 @@ wxString wxCheckListBoxComboPopup::GetStringValue() const
wxSize wxCheckListBoxComboPopup::GetAdjustedSize(int minWidth, int prefHeight, int maxHeight)
{
// matches owner wxComboCtrl's width
// and sets height dinamically in dependence of contained items count
wxComboCtrl* cmb = GetComboCtrl();
if (cmb != nullptr)
{
wxSize size = GetComboCtrl()->GetSize();
size.SetHeight(Height);
unsigned int count = GetCount();
if (count > 0)
size.SetHeight(count * DefaultItemHeight);
else
size.SetHeight(DefaultHeight);
return size;
}
else
return wxSize(200, Height);
return wxSize(DefaultWidth, DefaultHeight);
}
void wxCheckListBoxComboPopup::OnKeyEvent(wxKeyEvent& evt)
{
// filters out all the keys which are not working properly
switch (evt.GetKeyCode())
{
case WXK_LEFT:
case WXK_UP:
case WXK_RIGHT:
case WXK_DOWN:
case WXK_PAGEUP:
case WXK_PAGEDOWN:
case WXK_END:
case WXK_HOME:
case WXK_NUMPAD_LEFT:
case WXK_NUMPAD_UP:
case WXK_NUMPAD_RIGHT:
case WXK_NUMPAD_DOWN:
case WXK_NUMPAD_PAGEUP:
case WXK_NUMPAD_PAGEDOWN:
case WXK_NUMPAD_END:
case WXK_NUMPAD_HOME:
{
break;
}
default:
{
evt.Skip();
break;
}
}
}
void wxCheckListBoxComboPopup::OnCheckListBox(wxCommandEvent& evt)
@ -48,6 +89,8 @@ void wxCheckListBoxComboPopup::OnCheckListBox(wxCommandEvent& evt)
event.SetEventObject(cmb);
cmb->ProcessWindowEvent(event);
}
evt.Skip();
}
void wxCheckListBoxComboPopup::OnListBoxSelection(wxCommandEvent& evt)

View file

@ -6,7 +6,9 @@
class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup
{
static const unsigned int Height;
static const unsigned int DefaultWidth;
static const unsigned int DefaultHeight;
static const unsigned int DefaultItemHeight;
wxString m_text;
@ -17,6 +19,8 @@ public:
virtual wxString GetStringValue() const;
virtual wxSize GetAdjustedSize(int minWidth, int prefHeight, int maxHeight);
virtual void OnKeyEvent(wxKeyEvent& evt);
void OnCheckListBox(wxCommandEvent& evt);
void OnListBoxSelection(wxCommandEvent& evt);
};

View file

@ -0,0 +1,704 @@
#include "Bonjour.hpp"
#include <iostream> // XXX
#include <cstdint>
#include <algorithm>
#include <unordered_map>
#include <array>
#include <vector>
#include <string>
#include <random>
#include <thread>
#include <boost/optional.hpp>
#include <boost/system/error_code.hpp>
#include <boost/endian/conversion.hpp>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time_duration.hpp>
#include <boost/format.hpp>
using boost::optional;
using boost::system::error_code;
namespace endian = boost::endian;
namespace asio = boost::asio;
using boost::asio::ip::udp;
// TODO: Fuzzing test (done without TXT)
// FIXME: check char retype to unsigned
namespace Slic3r {
// Minimal implementation of a MDNS/DNS-SD client
// This implementation is extremely simple, only the bits that are useful
// for very basic MDNS discovery are present.
struct DnsName: public std::string
{
enum
{
MAX_RECURSION = 10, // Keep this low
};
static optional<DnsName> decode(const std::vector<char> &buffer, size_t &offset, unsigned depth = 0)
{
// Check offset sanity:
if (offset + 1 >= buffer.size()) {
return boost::none;
}
// Check for recursion depth to prevent parsing names that are nested too deeply
// or end up cyclic:
if (depth >= MAX_RECURSION) {
return boost::none;
}
DnsName res;
const size_t bsize = buffer.size();
while (true) {
const char* ptr = buffer.data() + offset;
unsigned len = static_cast<unsigned char>(*ptr);
if (len & 0xc0) {
// This is a recursive label
unsigned len_2 = static_cast<unsigned char>(ptr[1]);
size_t pointer = (len & 0x3f) << 8 | len_2;
const auto nested = decode(buffer, pointer, depth + 1);
if (!nested) {
return boost::none;
} else {
if (res.size() > 0) {
res.push_back('.');
}
res.append(*nested);
offset += 2;
return std::move(res);
}
} else if (len == 0) {
// This is a name terminator
offset++;
break;
} else {
// This is a regular label
len &= 0x3f;
if (len + offset + 1 >= bsize) {
return boost::none;
}
res.reserve(len);
if (res.size() > 0) {
res.push_back('.');
}
ptr++;
for (const auto end = ptr + len; ptr < end; ptr++) {
char c = *ptr;
if (c >= 0x20 && c <= 0x7f) {
res.push_back(c);
} else {
return boost::none;
}
}
offset += len + 1;
}
}
if (res.size() > 0) {
return std::move(res);
} else {
return boost::none;
}
}
};
struct DnsHeader
{
uint16_t id;
uint16_t flags;
uint16_t qdcount;
uint16_t ancount;
uint16_t nscount;
uint16_t arcount;
enum
{
SIZE = 12,
};
static DnsHeader decode(const std::vector<char> &buffer) {
DnsHeader res;
const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(buffer.data());
res.id = endian::big_to_native(data_16[0]);
res.flags = endian::big_to_native(data_16[1]);
res.qdcount = endian::big_to_native(data_16[2]);
res.ancount = endian::big_to_native(data_16[3]);
res.nscount = endian::big_to_native(data_16[4]);
res.arcount = endian::big_to_native(data_16[5]);
return res;
}
uint32_t rrcount() const {
return ancount + nscount + arcount;
}
};
struct DnsQuestion
{
enum
{
MIN_SIZE = 5,
};
DnsName name;
uint16_t type;
uint16_t qclass;
DnsQuestion() :
type(0),
qclass(0)
{}
static optional<DnsQuestion> decode(const std::vector<char> &buffer, size_t &offset)
{
auto qname = DnsName::decode(buffer, offset);
if (!qname) {
return boost::none;
}
DnsQuestion res;
res.name = std::move(*qname);
const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(buffer.data() + offset);
res.type = endian::big_to_native(data_16[0]);
res.qclass = endian::big_to_native(data_16[1]);
offset += 4;
return std::move(res);
}
};
struct DnsResource
{
DnsName name;
uint16_t type;
uint16_t rclass;
uint32_t ttl;
std::vector<char> data;
DnsResource() :
type(0),
rclass(0),
ttl(0)
{}
static optional<DnsResource> decode(const std::vector<char> &buffer, size_t &offset, size_t &dataoffset)
{
const size_t bsize = buffer.size();
if (offset + 1 >= bsize) {
return boost::none;
}
auto rname = DnsName::decode(buffer, offset);
if (!rname) {
return boost::none;
}
if (offset + 10 >= bsize) {
return boost::none;
}
DnsResource res;
res.name = std::move(*rname);
const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(buffer.data() + offset);
res.type = endian::big_to_native(data_16[0]);
res.rclass = endian::big_to_native(data_16[1]);
res.ttl = endian::big_to_native(*reinterpret_cast<const uint32_t*>(data_16 + 2));
uint16_t rdlength = endian::big_to_native(data_16[4]);
offset += 10;
if (offset + rdlength > bsize) {
return boost::none;
}
dataoffset = offset;
res.data = std::move(std::vector<char>(buffer.begin() + offset, buffer.begin() + offset + rdlength));
offset += rdlength;
return std::move(res);
}
};
struct DnsRR_A
{
enum { TAG = 0x1 };
asio::ip::address_v4 ip;
static void decode(optional<DnsRR_A> &result, const DnsResource &rr)
{
if (rr.data.size() == 4) {
DnsRR_A res;
const uint32_t ip = endian::big_to_native(*reinterpret_cast<const uint32_t*>(rr.data.data()));
res.ip = asio::ip::address_v4(ip);
result = std::move(res);
}
}
};
struct DnsRR_AAAA
{
enum { TAG = 0x1c };
asio::ip::address_v6 ip;
static void decode(optional<DnsRR_AAAA> &result, const DnsResource &rr)
{
if (rr.data.size() == 16) {
DnsRR_AAAA res;
std::array<unsigned char, 16> ip;
std::copy_n(rr.data.begin(), 16, ip.begin());
res.ip = asio::ip::address_v6(ip);
result = std::move(res);
}
}
};
struct DnsRR_SRV
{
enum
{
TAG = 0x21,
MIN_SIZE = 8,
};
uint16_t priority;
uint16_t weight;
uint16_t port;
DnsName hostname;
static optional<DnsRR_SRV> decode(const std::vector<char> &buffer, const DnsResource &rr, size_t dataoffset)
{
if (rr.data.size() < MIN_SIZE) {
return boost::none;
}
DnsRR_SRV res;
const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(rr.data.data());
res.priority = endian::big_to_native(data_16[0]);
res.weight = endian::big_to_native(data_16[1]);
res.port = endian::big_to_native(data_16[2]);
size_t offset = dataoffset + 6;
auto hostname = DnsName::decode(buffer, offset);
if (hostname) {
res.hostname = std::move(*hostname);
return std::move(res);
} else {
return boost::none;
}
}
};
struct DnsRR_TXT
{
enum
{
TAG = 0x10,
};
std::vector<std::string> values;
static optional<DnsRR_TXT> decode(const DnsResource &rr)
{
const size_t size = rr.data.size();
if (size < 2) {
return boost::none;
}
DnsRR_TXT res;
for (auto it = rr.data.begin(); it != rr.data.end(); ) {
unsigned val_size = static_cast<unsigned char>(*it);
if (val_size == 0 || it + val_size >= rr.data.end()) {
return boost::none;
}
++it;
std::string value(val_size, ' ');
std::copy(it, it + val_size, value.begin());
res.values.push_back(std::move(value));
it += val_size;
}
return std::move(res);
}
};
struct DnsSDPair
{
optional<DnsRR_SRV> srv;
optional<DnsRR_TXT> txt;
};
struct DnsSDMap : public std::map<std::string, DnsSDPair>
{
void insert_srv(std::string &&name, DnsRR_SRV &&srv)
{
auto hit = this->find(name);
if (hit != this->end()) {
hit->second.srv = std::move(srv);
} else {
DnsSDPair pair;
pair.srv = std::move(srv);
this->insert(std::make_pair(std::move(name), std::move(pair)));
}
}
void insert_txt(std::string &&name, DnsRR_TXT &&txt)
{
auto hit = this->find(name);
if (hit != this->end()) {
hit->second.txt = std::move(txt);
} else {
DnsSDPair pair;
pair.txt = std::move(txt);
this->insert(std::make_pair(std::move(name), std::move(pair)));
}
}
};
struct DnsMessage
{
enum
{
MAX_SIZE = 4096,
MAX_ANS = 30,
};
DnsHeader header;
optional<DnsQuestion> question;
optional<DnsRR_A> rr_a;
optional<DnsRR_AAAA> rr_aaaa;
std::vector<DnsRR_SRV> rr_srv;
DnsSDMap sdmap;
static optional<DnsMessage> decode(const std::vector<char> &buffer, optional<uint16_t> id_wanted = boost::none)
{
const auto size = buffer.size();
if (size < DnsHeader::SIZE + DnsQuestion::MIN_SIZE || size > MAX_SIZE) {
return boost::none;
}
DnsMessage res;
res.header = DnsHeader::decode(buffer);
if (id_wanted && *id_wanted != res.header.id) {
return boost::none;
}
if (res.header.qdcount > 1 || res.header.ancount > MAX_ANS) {
return boost::none;
}
size_t offset = DnsHeader::SIZE;
if (res.header.qdcount == 1) {
res.question = DnsQuestion::decode(buffer, offset);
}
for (unsigned i = 0; i < res.header.rrcount(); i++) {
size_t dataoffset = 0;
auto rr = DnsResource::decode(buffer, offset, dataoffset);
if (!rr) {
return boost::none;
} else {
res.parse_rr(buffer, std::move(*rr), dataoffset);
}
}
return std::move(res);
}
private:
void parse_rr(const std::vector<char> &buffer, DnsResource &&rr, size_t dataoffset)
{
switch (rr.type) {
case DnsRR_A::TAG: DnsRR_A::decode(this->rr_a, rr); break;
case DnsRR_AAAA::TAG: DnsRR_AAAA::decode(this->rr_aaaa, rr); break;
case DnsRR_SRV::TAG: {
auto srv = DnsRR_SRV::decode(buffer, rr, dataoffset);
if (srv) { this->sdmap.insert_srv(std::move(rr.name), std::move(*srv)); }
break;
}
case DnsRR_TXT::TAG: {
auto txt = DnsRR_TXT::decode(rr);
if (txt) { this->sdmap.insert_txt(std::move(rr.name), std::move(*txt)); }
break;
}
}
}
};
struct BonjourRequest
{
static const asio::ip::address_v4 MCAST_IP4;
static const uint16_t MCAST_PORT;
uint16_t id;
std::vector<char> data;
static optional<BonjourRequest> make(const std::string &service, const std::string &protocol);
private:
BonjourRequest(uint16_t id, std::vector<char> &&data) :
id(id),
data(std::move(data))
{}
};
const asio::ip::address_v4 BonjourRequest::MCAST_IP4{0xe00000fb};
const uint16_t BonjourRequest::MCAST_PORT = 5353;
optional<BonjourRequest> BonjourRequest::make(const std::string &service, const std::string &protocol)
{
if (service.size() > 15 || protocol.size() > 15) {
return boost::none;
}
std::random_device dev;
std::uniform_int_distribution<uint16_t> dist;
uint16_t id = dist(dev);
uint16_t id_big = endian::native_to_big(id);
const char *id_char = reinterpret_cast<char*>(&id_big);
std::vector<char> data;
data.reserve(service.size() + 18);
// Add the transaction ID
data.push_back(id_char[0]);
data.push_back(id_char[1]);
// Add metadata
static const unsigned char rq_meta[] = {
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
std::copy(rq_meta, rq_meta + sizeof(rq_meta), std::back_inserter(data));
// Add PTR query name
data.push_back(service.size() + 1);
data.push_back('_');
data.insert(data.end(), service.begin(), service.end());
data.push_back(protocol.size() + 1);
data.push_back('_');
data.insert(data.end(), protocol.begin(), protocol.end());
// Add the rest of PTR record
static const unsigned char ptr_tail[] = {
0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c, 0x00, 0xff,
};
std::copy(ptr_tail, ptr_tail + sizeof(ptr_tail), std::back_inserter(data));
return BonjourRequest(id, std::move(data));
}
// API - private part
struct Bonjour::priv
{
const std::string service;
const std::string protocol;
const std::string service_dn;
unsigned timeout;
uint16_t rq_id;
std::vector<char> buffer;
std::thread io_thread;
Bonjour::ReplyFn replyfn;
Bonjour::CompleteFn completefn;
priv(std::string service, std::string protocol);
void udp_receive(udp::endpoint from, size_t bytes);
void lookup_perform();
};
Bonjour::priv::priv(std::string service, std::string protocol) :
service(std::move(service)),
protocol(std::move(protocol)),
service_dn((boost::format("_%1%._%2%.local") % this->service % this->protocol).str()),
timeout(10),
rq_id(0)
{
buffer.resize(DnsMessage::MAX_SIZE);
}
void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes)
{
if (bytes == 0 || !replyfn) {
return;
}
buffer.resize(bytes);
const auto dns_msg = DnsMessage::decode(buffer, rq_id);
if (dns_msg) {
asio::ip::address ip = from.address();
if (dns_msg->rr_a) { ip = dns_msg->rr_a->ip; }
else if (dns_msg->rr_aaaa) { ip = dns_msg->rr_aaaa->ip; }
for (const auto &sdpair : dns_msg->sdmap) {
if (! sdpair.second.srv) {
continue;
}
const auto &srv = *sdpair.second.srv;
BonjourReply reply(ip, sdpair.first, srv.hostname);
if (sdpair.second.txt) {
static const std::string tag_path = "path=";
static const std::string tag_version = "version=";
for (const auto &value : sdpair.second.txt->values) {
if (value.size() > tag_path.size() && value.compare(0, tag_path.size(), tag_path) == 0) {
reply.path = value.substr(tag_path.size());
} else if (value.size() > tag_version.size() && value.compare(0, tag_version.size(), tag_version) == 0) {
reply.version = value.substr(tag_version.size());
}
}
}
replyfn(std::move(reply));
}
}
}
void Bonjour::priv::lookup_perform()
{
const auto brq = BonjourRequest::make(service, protocol);
if (!brq) {
return;
}
auto self = this;
rq_id = brq->id;
try {
boost::asio::io_service io_service;
udp::socket socket(io_service);
socket.open(udp::v4());
socket.set_option(udp::socket::reuse_address(true));
udp::endpoint mcast(BonjourRequest::MCAST_IP4, BonjourRequest::MCAST_PORT);
socket.send_to(asio::buffer(brq->data), mcast);
bool timeout = false;
asio::deadline_timer timer(io_service);
timer.expires_from_now(boost::posix_time::seconds(10));
timer.async_wait([=, &timeout](const error_code &error) {
timeout = true;
if (self->completefn) {
self->completefn();
}
});
udp::endpoint recv_from;
const auto recv_handler = [&](const error_code &error, size_t bytes) {
if (!error) { self->udp_receive(recv_from, bytes); }
};
socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
while (io_service.run_one()) {
if (timeout) {
socket.cancel();
} else {
buffer.resize(DnsMessage::MAX_SIZE);
socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
}
}
} catch (std::exception& e) {
}
}
// API - public part
BonjourReply::BonjourReply(boost::asio::ip::address ip, std::string service_name, std::string hostname) :
ip(std::move(ip)),
service_name(std::move(service_name)),
hostname(std::move(hostname)),
path("/"),
version("Unknown")
{}
std::ostream& operator<<(std::ostream &os, const BonjourReply &reply)
{
os << "BonjourReply(" << reply.ip.to_string() << ", " << reply.service_name << ", "
<< reply.hostname << ", " << reply.path << ", " << reply.version << ")";
return os;
}
Bonjour::Bonjour(std::string service, std::string protocol) :
p(new priv(std::move(service), std::move(protocol)))
{}
Bonjour::Bonjour(Bonjour &&other) : p(std::move(other.p)) {}
Bonjour::~Bonjour()
{
if (p && p->io_thread.joinable()) {
p->io_thread.detach();
}
}
Bonjour& Bonjour::set_timeout(unsigned timeout)
{
if (p) { p->timeout = timeout; }
return *this;
}
Bonjour& Bonjour::on_reply(ReplyFn fn)
{
if (p) { p->replyfn = std::move(fn); }
return *this;
}
Bonjour& Bonjour::on_complete(CompleteFn fn)
{
if (p) { p->completefn = std::move(fn); }
return *this;
}
Bonjour::Ptr Bonjour::lookup()
{
auto self = std::make_shared<Bonjour>(std::move(*this));
if (self->p) {
auto io_thread = std::thread([self](){
self->p->lookup_perform();
});
self->p->io_thread = std::move(io_thread);
}
return self;
}
void Bonjour::pokus() // XXX
{
auto bonjour = Bonjour("octoprint")
.set_timeout(15)
.on_reply([](BonjourReply &&reply) {
std::cerr << "BonjourReply: " << reply << std::endl;
})
.on_complete([](){
std::cerr << "MDNS lookup complete" << std::endl;
})
.lookup();
}
}

View file

@ -0,0 +1,56 @@
#ifndef slic3r_Bonjour_hpp_
#define slic3r_Bonjour_hpp_
#include <memory>
#include <string>
#include <functional>
// #include <ostream>
#include <boost/asio/ip/address.hpp>
namespace Slic3r {
// TODO: reply data structure
struct BonjourReply
{
boost::asio::ip::address ip;
std::string service_name;
std::string hostname;
std::string path;
std::string version;
BonjourReply(boost::asio::ip::address ip, std::string service_name, std::string hostname);
};
std::ostream& operator<<(std::ostream &, const BonjourReply &);
/// Bonjour lookup performer
class Bonjour : public std::enable_shared_from_this<Bonjour> {
private:
struct priv;
public:
typedef std::shared_ptr<Bonjour> Ptr;
typedef std::function<void(BonjourReply &&reply)> ReplyFn;
typedef std::function<void()> CompleteFn;
Bonjour(std::string service, std::string protocol = "tcp");
Bonjour(Bonjour &&other);
~Bonjour();
Bonjour& set_timeout(unsigned timeout);
Bonjour& on_reply(ReplyFn fn);
Bonjour& on_complete(CompleteFn fn);
Ptr lookup();
static void pokus(); // XXX: remove
private:
std::unique_ptr<priv> p;
};
}
#endif

View file

@ -0,0 +1,261 @@
#include "Http.hpp"
#include <cstdlib>
#include <functional>
#include <thread>
#include <iostream>
#include <tuple>
#include <boost/format.hpp>
#include <curl/curl.h>
#include "../../libslic3r/libslic3r.h"
namespace Slic3r {
// Private
class CurlGlobalInit
{
static const CurlGlobalInit instance;
CurlGlobalInit() { ::curl_global_init(CURL_GLOBAL_DEFAULT); }
~CurlGlobalInit() { ::curl_global_cleanup(); }
};
struct Http::priv
{
enum {
DEFAULT_SIZE_LIMIT = 5 * 1024 * 1024,
};
::CURL *curl;
::curl_httppost *form;
::curl_httppost *form_end;
::curl_slist *headerlist;
std::string buffer;
size_t limit;
std::thread io_thread;
Http::CompleteFn completefn;
Http::ErrorFn errorfn;
priv(const std::string &url);
~priv();
static size_t writecb(void *data, size_t size, size_t nmemb, void *userp);
std::string body_size_error();
void http_perform();
};
Http::priv::priv(const std::string &url) :
curl(::curl_easy_init()),
form(nullptr),
form_end(nullptr),
headerlist(nullptr)
{
if (curl == nullptr) {
throw std::runtime_error(std::string("Could not construct Curl object"));
}
::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally
::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_FORK_NAME "/" SLIC3R_VERSION);
}
Http::priv::~priv()
{
::curl_easy_cleanup(curl);
::curl_formfree(form);
::curl_slist_free_all(headerlist);
}
size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
{
auto self = static_cast<priv*>(userp);
const char *cdata = static_cast<char*>(data);
const size_t realsize = size * nmemb;
const size_t limit = self->limit > 0 ? self->limit : DEFAULT_SIZE_LIMIT;
if (self->buffer.size() + realsize > limit) {
// This makes curl_easy_perform return CURLE_WRITE_ERROR
return 0;
}
self->buffer.append(cdata, realsize);
return realsize;
}
std::string Http::priv::body_size_error()
{
return (boost::format("HTTP body data size exceeded limit (%1% bytes)") % limit).str();
}
void Http::priv::http_perform()
{
::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb);
::curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void*>(this));
#ifndef NDEBUG
::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
#endif
if (headerlist != nullptr) {
::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
}
if (form != nullptr) {
::curl_easy_setopt(curl, CURLOPT_HTTPPOST, form);
}
CURLcode res = ::curl_easy_perform(curl);
long http_status = 0;
::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status);
if (res != CURLE_OK) {
std::string error;
if (res == CURLE_WRITE_ERROR) {
error = std::move(body_size_error());
} else {
error = ::curl_easy_strerror(res);
};
if (errorfn) {
errorfn(std::move(buffer), std::move(error), http_status);
}
} else {
if (completefn) {
completefn(std::move(buffer), http_status);
}
}
}
Http::Http(const std::string &url) : p(new priv(url)) {}
// Public
Http::Http(Http &&other) : p(std::move(other.p)) {}
Http::~Http()
{
if (p && p->io_thread.joinable()) {
p->io_thread.detach();
}
}
Http& Http::size_limit(size_t sizeLimit)
{
if (p) { p->limit = sizeLimit; }
return *this;
}
Http& Http::header(std::string name, const std::string &value)
{
if (!p) { return * this; }
if (name.size() > 0) {
name.append(": ").append(value);
} else {
name.push_back(':');
}
p->headerlist = curl_slist_append(p->headerlist, name.c_str());
return *this;
}
Http& Http::remove_header(std::string name)
{
if (p) {
name.push_back(':');
p->headerlist = curl_slist_append(p->headerlist, name.c_str());
}
return *this;
}
Http& Http::ca_file(const std::string &name)
{
if (p) {
::curl_easy_setopt(p->curl, CURLOPT_CAINFO, name.c_str());
}
return *this;
}
Http& Http::form_add(const std::string &name, const std::string &contents)
{
if (p) {
::curl_formadd(&p->form, &p->form_end,
CURLFORM_COPYNAME, name.c_str(),
CURLFORM_COPYCONTENTS, contents.c_str(),
CURLFORM_END
);
}
return *this;
}
Http& Http::form_add_file(const std::string &name, const std::string &filename)
{
if (p) {
::curl_formadd(&p->form, &p->form_end,
CURLFORM_COPYNAME, name.c_str(),
CURLFORM_FILE, filename.c_str(),
CURLFORM_CONTENTTYPE, "application/octet-stream",
CURLFORM_END
);
}
return *this;
}
Http& Http::on_complete(CompleteFn fn)
{
if (p) { p->completefn = std::move(fn); }
return *this;
}
Http& Http::on_error(ErrorFn fn)
{
if (p) { p->errorfn = std::move(fn); }
return *this;
}
Http::Ptr Http::perform()
{
auto self = std::make_shared<Http>(std::move(*this));
if (self->p) {
auto io_thread = std::thread([self](){
self->p->http_perform();
});
self->p->io_thread = std::move(io_thread);
}
return self;
}
void Http::perform_sync()
{
if (p) { p->http_perform(); }
}
Http Http::get(std::string url)
{
return std::move(Http{std::move(url)});
}
Http Http::post(std::string url)
{
Http http{std::move(url)};
curl_easy_setopt(http.p->curl, CURLOPT_POST, 1L);
return http;
}
}

View file

@ -0,0 +1,53 @@
#ifndef slic3r_Http_hpp_
#define slic3r_Http_hpp_
#include <memory>
#include <string>
#include <functional>
namespace Slic3r {
/// Represetns a Http request
class Http : public std::enable_shared_from_this<Http> {
private:
struct priv;
public:
typedef std::shared_ptr<Http> Ptr;
typedef std::function<void(std::string /* body */, unsigned /* http_status */)> CompleteFn;
typedef std::function<void(std::string /* body */, std::string /* error */, unsigned /* http_status */)> ErrorFn;
Http(Http &&other);
static Http get(std::string url);
static Http post(std::string url);
~Http();
Http(const Http &) = delete;
Http& operator=(const Http &) = delete;
Http& operator=(Http &&) = delete;
Http& size_limit(size_t sizeLimit);
Http& header(std::string name, const std::string &value);
Http& remove_header(std::string name);
Http& ca_file(const std::string &filename);
Http& form_add(const std::string &name, const std::string &contents);
Http& form_add_file(const std::string &name, const std::string &filename);
Http& on_complete(CompleteFn fn);
Http& on_error(ErrorFn fn);
Ptr perform();
void perform_sync();
private:
Http(const std::string &url);
std::unique_ptr<priv> p;
};
}
#endif

View file

@ -0,0 +1,105 @@
#include "OctoPrint.hpp"
#include <iostream>
#include <boost/format.hpp>
#include <wx/frame.h>
#include <wx/event.h>
#include "libslic3r/PrintConfig.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "Http.hpp"
namespace Slic3r {
OctoPrint::OctoPrint(DynamicPrintConfig *config) :
host(config->opt_string("octoprint_host")),
apikey(config->opt_string("octoprint_apikey")),
cafile(config->opt_string("octoprint_cafile"))
{}
std::string OctoPrint::test() const
{
// Since the request is performed synchronously here,
// it is ok to refer to `res` from within the closure
std::string res;
auto http = Http::get(std::move(make_url("api/version")));
set_auth(http);
http.on_error([&](std::string, std::string error, unsigned status) {
res = format_error(error, status);
})
.perform_sync();
return res;
}
void OctoPrint::send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print) const
{
auto http = Http::post(std::move(make_url("api/files/local")));
set_auth(http);
http.form_add("print", print ? "true" : "false")
.form_add_file("file", filename)
.on_complete([=](std::string body, unsigned status) {
wxWindow *window = GUI::get_widget_by_id(windowId);
wxCommandEvent* evt = new wxCommandEvent(completeEvt);
evt->SetString("G-code file successfully uploaded to the OctoPrint server");
evt->SetInt(100);
wxQueueEvent(window, evt);
})
.on_error([=](std::string body, std::string error, unsigned status) {
wxWindow *window = GUI::get_widget_by_id(windowId);
wxCommandEvent* evt_complete = new wxCommandEvent(completeEvt);
evt_complete->SetInt(100);
wxQueueEvent(window, evt_complete);
wxCommandEvent* evt_error = new wxCommandEvent(errorEvt);
evt_error->SetString(wxString::Format("Error while uploading to the OctoPrint server: %s", format_error(error, status)));
wxQueueEvent(window, evt_error);
})
.perform();
}
void OctoPrint::set_auth(Http &http) const
{
http.header("X-Api-Key", apikey);
if (! cafile.empty()) {
http.ca_file(cafile);
}
}
std::string OctoPrint::make_url(const std::string &path) const
{
if (host.find("http://") == 0 || host.find("https://") == 0) {
if (host.back() == '/') {
return std::move((boost::format("%1%%2%") % host % path).str());
} else {
return std::move((boost::format("%1%/%2%") % host % path).str());
}
} else {
return std::move((boost::format("http://%1%/%2%") % host % path).str());
}
}
std::string OctoPrint::format_error(std::string error, unsigned status)
{
if (status != 0) {
std::string res{"HTTP "};
res.append(std::to_string(status));
if (status == 401) {
res.append(": Invalid API key");
}
return std::move(res);
} else {
return std::move(error);
}
}
}

View file

@ -0,0 +1,35 @@
#ifndef slic3r_OctoPrint_hpp_
#define slic3r_OctoPrint_hpp_
#include <string>
// #include "Http.hpp" // XXX: ?
namespace Slic3r {
class DynamicPrintConfig;
class Http;
class OctoPrint
{
public:
OctoPrint(DynamicPrintConfig *config);
std::string test() const;
// XXX: style
void send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print = false) const;
private:
std::string host;
std::string apikey;
std::string cafile;
void set_auth(Http &http) const;
std::string make_url(const std::string &path) const;
static std::string format_error(std::string error, unsigned status);
};
}
#endif

View file

@ -59,6 +59,15 @@ extern "C" {
#undef seek
#undef send
#undef write
#undef open
#undef close
#undef seekdir
#undef setbuf
#undef fread
#undef fseek
#undef fputc
#undef fwrite
#undef fclose
#endif /* _MSC_VER */
}
#endif

View file

@ -35,11 +35,11 @@ void set_tab_panel(SV *ui)
void add_debug_menu(SV *ui, int event_language_change)
%code%{ Slic3r::GUI::add_debug_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar"), event_language_change); %};
void create_preset_tabs(PresetBundle *preset_bundle, AppConfig *app_config,
bool no_controller, bool is_disabled_button_browse, bool is_user_agent,
void create_preset_tabs(PresetBundle *preset_bundle, bool no_controller,
bool is_disabled_button_browse, bool is_user_agent,
int event_value_change, int event_presets_changed,
int event_button_browse, int event_button_test)
%code%{ Slic3r::GUI::create_preset_tabs(preset_bundle, app_config, no_controller,
%code%{ Slic3r::GUI::create_preset_tabs(preset_bundle, no_controller,
is_disabled_button_browse, is_user_agent,
event_value_change, event_presets_changed,
event_button_browse, event_button_test); %};
@ -55,3 +55,9 @@ void create_combochecklist(SV *ui, std::string text, std::string items, bool ini
int combochecklist_get_flags(SV *ui)
%code%{ RETVAL=Slic3r::GUI::combochecklist_get_flags((wxComboCtrl*)wxPli_sv_2_object(aTHX_ ui, "Wx::ComboCtrl")); %};
void set_app_config(AppConfig *app_config)
%code%{ Slic3r::GUI::set_app_config(app_config); %};
void open_preferences_dialog(int preferences_event)
%code%{ Slic3r::GUI::open_preferences_dialog(preferences_event); %};

View file

@ -85,7 +85,11 @@
int count()
%code{% RETVAL = THIS->volumes.size(); %};
std::vector<double> get_current_print_zs()
%code{% RETVAL = THIS->get_current_print_zs(); %};
void set_range(double low, double high);
void render_VBOs() const;

View file

@ -0,0 +1,14 @@
%module{Slic3r::XS};
%{
#include <xsinit.h>
#include "slic3r/Utils/OctoPrint.hpp"
%}
%name{Slic3r::OctoPrint} class OctoPrint {
OctoPrint(DynamicPrintConfig *config);
~OctoPrint();
std::string test() const;
void send_gcode(int windowId, int completeEvt, int errorEvt, std::string filename, bool print = false) const;
};

View file

@ -236,6 +236,10 @@ Ref<PresetHints> O_OBJECT_SLIC3R_T
TabIface* O_OBJECT_SLIC3R
Ref<TabIface> O_OBJECT_SLIC3R_T
OctoPrint* O_OBJECT_SLIC3R
Ref<OctoPrint> O_OBJECT_SLIC3R_T
Clone<OctoPrint> O_OBJECT_SLIC3R_T
Axis T_UV
ExtrusionLoopRole T_UV
ExtrusionRole T_UV