mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-21 15:51:10 -06:00
Merge branch 'master' into wipe_tower_improvements
This commit is contained in:
commit
1c6fa6660e
79 changed files with 34645 additions and 10116 deletions
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ enum ExtrusionRole {
|
|||
erSupportMaterial,
|
||||
erSupportMaterialInterface,
|
||||
erWipeTower,
|
||||
erCustom,
|
||||
// Extrusion role for a collection with multiple extrusion roles.
|
||||
erMixed,
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
150
xs/src/libslic3r/Fill/FillGyroid.cpp
Normal file
150
xs/src/libslic3r/Fill/FillGyroid.cpp
Normal 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 ¶ms,
|
||||
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
|
34
xs/src/libslic3r/Fill/FillGyroid.hpp
Normal file
34
xs/src/libslic3r/Fill/FillGyroid.hpp
Normal 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 ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon &expolygon,
|
||||
Polylines &polylines_out);
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillGyroid_hpp_
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 &);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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(){ }
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(); }
|
||||
|
|
120
xs/src/slic3r/GUI/Preferences.cpp
Normal file
120
xs/src/slic3r/GUI/Preferences.cpp
Normal 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
|
27
xs/src/slic3r/GUI/Preferences.hpp
Normal file
27
xs/src/slic3r/GUI/Preferences.hpp
Normal 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
|
||||
|
|
@ -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"
|
||||
};
|
||||
|
|
|
@ -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 §ion : 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()), §ion.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;
|
||||
|
|
|
@ -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
|
@ -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;
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
704
xs/src/slic3r/Utils/Bonjour.cpp
Normal file
704
xs/src/slic3r/Utils/Bonjour.cpp
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
56
xs/src/slic3r/Utils/Bonjour.hpp
Normal file
56
xs/src/slic3r/Utils/Bonjour.hpp
Normal 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
|
261
xs/src/slic3r/Utils/Http.cpp
Normal file
261
xs/src/slic3r/Utils/Http.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
53
xs/src/slic3r/Utils/Http.hpp
Normal file
53
xs/src/slic3r/Utils/Http.hpp
Normal 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
|
105
xs/src/slic3r/Utils/OctoPrint.cpp
Normal file
105
xs/src/slic3r/Utils/OctoPrint.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
35
xs/src/slic3r/Utils/OctoPrint.hpp
Normal file
35
xs/src/slic3r/Utils/OctoPrint.hpp
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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); %};
|
||||
|
|
|
@ -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;
|
||||
|
|
14
xs/xsp/Utils_OctoPrint.xsp
Normal file
14
xs/xsp/Utils_OctoPrint.xsp
Normal 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;
|
||||
};
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue