This commit is contained in:
enricoturri1966 2020-12-08 15:56:08 +01:00
commit b7ac74bc42
53 changed files with 1789 additions and 915 deletions

View file

@ -124,45 +124,33 @@ intermediate files, which are not handled correctly by either `b2.exe` or possib
# Noob guide (step by step)
Install Visual Studio Community 2019 from
[visualstudio.microsoft.com/vs/](https://visualstudio.microsoft.com/vs/)
Select all workload options for C++
- Install Visual Studio Community 2019 from [visualstudio.microsoft.com/vs/](https://visualstudio.microsoft.com/vs/)
- Select all workload options for C++
- Install git for Windows from [gitforwindows.org](https://gitforwindows.org/)
- download and run the exe accepting all defaults
- Download `PrusaSlicer-master.zip` from github
- This example will use the directory c:\PrusaSlicer and unzipped to `c:\PrusaSlicer\PrusaSlicer-master\` so this will be the prefix for all the steps. Substitute your as required prefix.
- Go to the Windows Start Menu and Click on "Visual Studio 2019" folder, then select the ->"x64 Native Tools Command Prompt" to open a command window
Install git for Windows from
[gitforwindows.org](https://gitforwindows.org/)
download and run the exe accepting all defaults
cd c:\PrusaSlicer\PrusaSlicer-master\deps
mkdir build
cd build
cmake .. -G "Visual Studio 16 2019" -DDESTDIR="c:\PrusaSlicer\PrusaSlicer-master"
msbuild /m ALL_BUILD.vcxproj // This took 13.5 minutes on the following machine: core I7-7700K @ 4.2Ghz with 32GB main memory and 20min on an average laptop
cd c:\PrusaSlicer\PrusaSlicer-master\
mkdir build
cd build
cmake .. -G "Visual Studio 16 2019" -DCMAKE_PREFIX_PATH="c:\PrusaSlicer\PrusaSlicer-master\usr\local"
download PrusaSlicer-master.zip from github
I downloaded this to c:\PrusaSlicer and unzipped to c:\PrusaSlicer\PrusaSlicer-master\ so this will be my prefix for all my steps. Substitute your prefix.
- Open Visual Studio for c++ development (VS asks this the first time you start it)
Go to the Windows Start Menu and Click on "Visual Studio 2019" folder, then select the ->"x64 Native Tools Command Prompt" to open a command window
`Open->Project/Solution` or `File->Open->Project/Solution` (depending on which dialog comes up first)
cd c:\PrusaSlicer\PrusaSlicer-master\deps
- Click on `c:\PrusaSlicer\PrusaSlicer-master\build\PrusaSlicer.sln`
mkdir build
`Debug->Start Debugging` or `Debug->Start Without debugging`
cd build
- PrusaSlicer should start.
- You're up and running!
cmake .. -G "Visual Studio 16 2019" -DDESTDIR="c:\PrusaSlicer\PrusaSlicer-master"
msbuild /m ALL_BUILD.vcxproj // This took 13.5 minutes on my machine: core I7-7700K @ 4.2Ghz with 32GB main memory and 20min on a average laptop
cd c:\PrusaSlicer\PrusaSlicer-master\
mkdir build
cd build
cmake .. -G "Visual Studio 16 2019" -DCMAKE_PREFIX_PATH="c:\PrusaSlicer\PrusaSlicer-master\usr\local"
open Visual Studio for c++ development (VS asks this the first time you start it)
Open->Project/Solution or File->Open->Project/Solution (depending on which dialog comes up first)
click on c:\PrusaSlicer\PrusaSlicer-master\build\PrusaSlicer.sln
Debug->Start Debugging or Debug->Start Without debugging
PrusaSlicer should start. You're up and running!
note: Thanks to @douggorgen for the original guide, as an answer for a issue
Note: Thanks to @douggorgen for the original guide, as an answer for a issue

View file

@ -34,6 +34,7 @@
#include "libslic3r/libslic3r.h"
#include "libslic3r/Config.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/GCode/PostProcessor.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/ModelArrange.hpp"
#include "libslic3r/Print.hpp"
@ -483,6 +484,12 @@ int CLI::run(int argc, char **argv)
if (printer_technology == ptFFF) {
for (auto* mo : model.objects)
fff_print.auto_assign_extruders(mo);
} else {
// The default for "output_filename_format" is good for FDM: "[input_filename_base].gcode"
// Replace it with a reasonable SLA default.
std::string &format = m_print_config.opt_string("output_filename_format", true);
if (format == static_cast<const ConfigOptionString*>(m_print_config.def()->get("output_filename_format")->default_value.get())->value)
format = "[input_filename_base].SL1";
}
print->apply(model, m_print_config);
std::string err = print->validate();
@ -513,6 +520,8 @@ int CLI::run(int argc, char **argv)
}
outfile = outfile_final;
}
// Run the post-processing scripts if defined.
run_post_process_scripts(outfile, fff_print.full_print_config());
boost::nowide::cout << "Slicing result exported to " << outfile << std::endl;
} catch (const std::exception &ex) {
boost::nowide::cerr << ex.what() << std::endl;

View file

@ -99,6 +99,8 @@ add_library(libslic3r STATIC
GCode/WipeTower.hpp
GCode/GCodeProcessor.cpp
GCode/GCodeProcessor.hpp
GCode/AvoidCrossingPerimeters.cpp
GCode/AvoidCrossingPerimeters.hpp
GCode.cpp
GCode.hpp
GCodeReader.cpp
@ -126,8 +128,6 @@ add_library(libslic3r STATIC
CustomGCode.hpp
Arrange.hpp
Arrange.cpp
MotionPlanner.cpp
MotionPlanner.hpp
MultiPoint.cpp
MultiPoint.hpp
MutablePriorityQueue.hpp

View file

@ -259,7 +259,14 @@ public:
{
const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first];
size_t ipt = contour_and_segment_idx.second;
return std::pair<const Slic3r::Point&, const Slic3r::Point&>(ipts[ipt], ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]);
return std::pair<const Slic3r::Point&, const Slic3r::Point&>(ipts[ipt], ipts[ipt + 1 == ipts.size() ? 0 : ipt + 1]);
}
Line line(const std::pair<size_t, size_t> &contour_and_segment_idx) const
{
const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first];
size_t ipt = contour_and_segment_idx.second;
return Line(ipts[ipt], ipts[ipt + 1 == ipts.size() ? 0 : ipt + 1]);
}
protected:

View file

@ -254,11 +254,12 @@ std::vector<float> contour_distance2(const EdgeGrid::Grid &grid, const size_t id
grid(grid), idx_contour(idx_contour), contour(*grid.contours()[idx_contour]), resampled_point_parameters(resampled_point_parameters), dist_same_contour_accept(dist_same_contour_accept), dist_same_contour_reject(dist_same_contour_reject) {}
void init(const Points &contour, const Point &apoint) {
this->idx_point = &apoint - contour.data();
this->point = apoint;
this->found = false;
this->dir_inside = this->dir_inside_at_point(contour, this->idx_point);
}
this->idx_point = &apoint - contour.data();
this->point = apoint;
this->found = false;
this->dir_inside = this->dir_inside_at_point(contour, this->idx_point);
this->distance = std::numeric_limits<double>::max();
}
bool operator()(coord_t iy, coord_t ix) {
// Called with a row and colum of the grid cell, which is intersected by a line.

View file

@ -42,11 +42,11 @@ void ExPolygon::scale(double factor)
hole.scale(factor);
}
void ExPolygon::translate(double x, double y)
void ExPolygon::translate(const Point &p)
{
contour.translate(x, y);
contour.translate(p);
for (Polygon &hole : holes)
hole.translate(x, y);
hole.translate(p);
}
void ExPolygon::rotate(double angle)

View file

@ -42,7 +42,8 @@ public:
operator Polylines() const;
void clear() { contour.points.clear(); holes.clear(); }
void scale(double factor);
void translate(double x, double y);
void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); }
void translate(const Point &vector);
void rotate(double angle);
void rotate(double angle, const Point &center);
double area() const;

View file

@ -1237,7 +1237,7 @@ static void pinch_contours_insert_phony_outer_intersections(std::vector<Segmente
for (SegmentIntersection &ip : segs[i_vline - 1].intersections)
if (ip.has_right_horizontal())
ip.next_on_contour = map[ip.next_on_contour];
if (i_vline < segs.size()) {
if (i_vline + 1 < segs.size()) {
// Reindex references on next intersection line.
for (SegmentIntersection &ip : segs[i_vline + 1].intersections)
if (ip.has_left_horizontal())

View file

@ -10,6 +10,7 @@
#include "ShortestPath.hpp"
#include "Print.hpp"
#include "Utils.hpp"
#include "ClipperUtils.hpp"
#include "libslic3r.h"
#include <algorithm>
@ -89,94 +90,6 @@ namespace Slic3r {
return ok;
}
void AvoidCrossingPerimeters::init_external_mp(const Print& print)
{
m_external_mp = Slic3r::make_unique<MotionPlanner>(union_ex(this->collect_contours_all_layers(print.objects())));
}
// Plan a travel move while minimizing the number of perimeter crossings.
// point is in unscaled coordinates, in the coordinate system of the current active object
// (set by gcodegen.set_origin()).
Polyline AvoidCrossingPerimeters::travel_to(const GCode& gcodegen, const Point& point)
{
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
// Otherwise perform the path planning in the coordinate system of the active object.
bool use_external = this->use_external_mp || this->use_external_mp_once;
Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0);
Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())->
shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin);
if (use_external)
result.translate(-scaled_origin);
return result;
}
// Collect outer contours of all objects over all layers.
// Discard objects only containing thin walls (offset would fail on an empty polygon).
// Used by avoid crossing perimeters feature.
Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectPtrs& objects)
{
Polygons islands;
for (const PrintObject* object : objects) {
// Reducing all the object slices into the Z projection in a logarithimc fashion.
// First reduce to half the number of layers.
std::vector<Polygons> polygons_per_layer((object->layers().size() + 1) / 2);
tbb::parallel_for(tbb::blocked_range<size_t>(0, object->layers().size() / 2),
[&object, &polygons_per_layer](const tbb::blocked_range<size_t>& range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
const Layer* layer1 = object->layers()[i * 2];
const Layer* layer2 = object->layers()[i * 2 + 1];
Polygons polys;
polys.reserve(layer1->lslices.size() + layer2->lslices.size());
for (const ExPolygon& expoly : layer1->lslices)
//FIXME no holes?
polys.emplace_back(expoly.contour);
for (const ExPolygon& expoly : layer2->lslices)
//FIXME no holes?
polys.emplace_back(expoly.contour);
polygons_per_layer[i] = union_(polys);
}
});
if (object->layers().size() & 1) {
const Layer* layer = object->layers().back();
Polygons polys;
polys.reserve(layer->lslices.size());
for (const ExPolygon& expoly : layer->lslices)
//FIXME no holes?
polys.emplace_back(expoly.contour);
polygons_per_layer.back() = union_(polys);
}
// Now reduce down to a single layer.
size_t cnt = polygons_per_layer.size();
while (cnt > 1) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, cnt / 2),
[&polygons_per_layer](const tbb::blocked_range<size_t>& range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
Polygons polys;
polys.reserve(polygons_per_layer[i * 2].size() + polygons_per_layer[i * 2 + 1].size());
polygons_append(polys, polygons_per_layer[i * 2]);
polygons_append(polys, polygons_per_layer[i * 2 + 1]);
polygons_per_layer[i * 2] = union_(polys);
}
});
for (size_t i = 1; i < cnt / 2; ++i)
polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]);
if (cnt & 1)
polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]);
cnt = (cnt + 1) / 2;
}
// And collect copies of the objects.
for (const PrintInstance& instance : object->instances()) {
// All the layers were reduced to the 1st item of polygons_per_layer.
size_t i = islands.size();
polygons_append(islands, polygons_per_layer.front());
for (; i < islands.size(); ++i)
islands[i].translate(instance.shift);
}
}
return islands;
}
std::string OozePrevention::pre_toolchange(GCode& gcodegen)
{
std::string gcode;
@ -322,7 +235,7 @@ namespace Slic3r {
// Move over the wipe tower.
// Retract for a tool change, using the toolchange retract value and setting the priming extra length.
gcode += gcodegen.retract(true);
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
gcode += gcodegen.travel_to(
wipe_tower_point_to_object_point(gcodegen, start_pos),
erMixed,
@ -419,7 +332,7 @@ namespace Slic3r {
}
// Let the planner know we are traveling between objects.
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
return gcode;
}
@ -1240,13 +1153,6 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
// Set other general things.
_write(file, this->preamble());
// Initialize a motion planner for object-to-object travel moves.
m_avoid_crossing_perimeters.reset();
if (print.config().avoid_crossing_perimeters.value) {
m_avoid_crossing_perimeters.init_external_mp(print);
print.throw_if_canceled();
}
// Calculate wiping points if needed
DoExport::init_ooze_prevention(print, m_ooze_prevention);
print.throw_if_canceled();
@ -1282,12 +1188,12 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
// Move to the origin position for the copy we're going to print.
// This happens before Z goes down to layer 0 again, so that no collision happens hopefully.
m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer
m_avoid_crossing_perimeters.use_external_mp_once = true;
m_avoid_crossing_perimeters.use_external_mp_once();
_write(file, this->retract());
_write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object"));
m_enable_cooling_markers = true;
// Disable motion planner when traveling to first object point.
m_avoid_crossing_perimeters.disable_once = true;
m_avoid_crossing_perimeters.disable_once();
// Ff we are printing the bottom layer of an object, and we have already finished
// another one, set first layer temperatures. This happens before the Z move
// is triggered, so machine has more time to reach such temperatures.
@ -2113,7 +2019,7 @@ void GCode::process_layer(
if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) {
const std::pair<size_t, size_t> loops = loops_it->second;
this->set_origin(0., 0.);
m_avoid_crossing_perimeters.use_external_mp = true;
m_avoid_crossing_perimeters.use_external_mp();
Flow layer_skirt_flow(print.skirt_flow());
layer_skirt_flow.height = float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]));
double mm3_per_mm = layer_skirt_flow.mm3_per_mm();
@ -2127,23 +2033,23 @@ void GCode::process_layer(
//FIXME using the support_material_speed of the 1st object printed.
gcode += this->extrude_loop(loop, "skirt", m_config.support_material_speed.value);
}
m_avoid_crossing_perimeters.use_external_mp = false;
m_avoid_crossing_perimeters.use_external_mp(false);
// Allow a straight travel move to the first object point if this is the first layer (but don't in next layers).
if (first_layer && loops.first == 0)
m_avoid_crossing_perimeters.disable_once = true;
m_avoid_crossing_perimeters.disable_once();
}
// Extrude brim with the extruder of the 1st region.
if (! m_brim_done) {
this->set_origin(0., 0.);
m_avoid_crossing_perimeters.use_external_mp = true;
m_avoid_crossing_perimeters.use_external_mp();
for (const ExtrusionEntity *ee : print.brim().entities) {
gcode += this->extrude_entity(*ee, "brim", m_config.support_material_speed.value);
}
m_brim_done = true;
m_avoid_crossing_perimeters.use_external_mp = false;
m_avoid_crossing_perimeters.use_external_mp(false);
// Allow a straight travel move to the first object point.
m_avoid_crossing_perimeters.disable_once = true;
m_avoid_crossing_perimeters.disable_once();
}
@ -2163,15 +2069,14 @@ void GCode::process_layer(
m_config.apply(instance_to_print.print_object.config(), true);
m_layer = layers[instance_to_print.layer_id].layer();
if (m_config.avoid_crossing_perimeters)
m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->lslices, true));
m_avoid_crossing_perimeters.init_layer(*m_layer);
if (this->config().gcode_label_objects)
gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
// When starting a new object, use the external motion planner for the first travel move.
const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift;
std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset);
if (m_last_obj_copy != this_object_copy)
m_avoid_crossing_perimeters.use_external_mp_once = true;
m_avoid_crossing_perimeters.use_external_mp_once();
m_last_obj_copy = this_object_copy;
this->set_origin(unscale(offset));
if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) {
@ -2773,43 +2678,51 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string
/* Define the travel move as a line between current position and the taget point.
This is expressed in print coordinates, so it will need to be translated by
this->origin in order to get G-code coordinates. */
Polyline travel;
travel.append(this->last_pos());
travel.append(point);
Polyline travel { this->last_pos(), point };
// check whether a straight travel move would need retraction
bool needs_retraction = this->needs_retraction(travel, role);
bool needs_retraction = this->needs_retraction(travel, role);
// check whether wipe could be disabled without causing visible stringing
bool could_be_wipe_disabled = false;
// if a retraction would be needed, try to use avoid_crossing_perimeters to plan a
// multi-hop travel path inside the configuration space
if (needs_retraction
&& m_config.avoid_crossing_perimeters
&& ! m_avoid_crossing_perimeters.disable_once) {
travel = m_avoid_crossing_perimeters.travel_to(*this, point);
&& ! m_avoid_crossing_perimeters.disabled_once()) {
travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled);
// check again whether the new travel path still needs a retraction
needs_retraction = this->needs_retraction(travel, role);
//if (needs_retraction && m_layer_index > 1) exit(0);
}
// Re-allow avoid_crossing_perimeters for the next travel moves
m_avoid_crossing_perimeters.disable_once = false;
m_avoid_crossing_perimeters.use_external_mp_once = false;
m_avoid_crossing_perimeters.reset_once_modifiers();
// generate G-code for the travel move
std::string gcode;
if (needs_retraction)
if (needs_retraction) {
if (m_config.avoid_crossing_perimeters && could_be_wipe_disabled)
m_wipe.reset_path();
Point last_post_before_retract = this->last_pos();
gcode += this->retract();
else
// When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters.
// Because of it, it is necessary to call avoid crossing perimeters for the path between previous last_post and last_post after calling retraction()
if (last_post_before_retract != this->last_pos() && m_config.avoid_crossing_perimeters) {
Polyline retract_travel = m_avoid_crossing_perimeters.travel_to(*this, last_post_before_retract);
append(retract_travel.points, travel.points);
travel = std::move(retract_travel);
}
} else
// Reset the wipe path when traveling, so one would not wipe along an old path.
m_wipe.reset_path();
// use G1 because we rely on paths being straight (G0 may make round paths)
Lines lines = travel.lines();
if (! lines.empty()) {
for (const Line &line : lines)
gcode += m_writer.travel_to_xy(this->point_to_gcode(line.b), comment);
this->set_last_pos(lines.back().b);
if (travel.size() >= 2) {
for (size_t i = 1; i < travel.size(); ++ i)
gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment);
this->set_last_pos(travel.points.back());
}
return gcode;
}

View file

@ -5,10 +5,10 @@
#include "ExPolygon.hpp"
#include "GCodeWriter.hpp"
#include "Layer.hpp"
#include "MotionPlanner.hpp"
#include "Point.hpp"
#include "PlaceholderParser.hpp"
#include "PrintConfig.hpp"
#include "GCode/AvoidCrossingPerimeters.hpp"
#include "GCode/CoolingBuffer.hpp"
#include "GCode/SpiralVase.hpp"
#include "GCode/ToolOrdering.hpp"
@ -35,35 +35,6 @@ namespace { struct Item; }
struct PrintInstance;
using PrintObjectPtrs = std::vector<PrintObject*>;
class AvoidCrossingPerimeters {
public:
// this flag triggers the use of the external configuration space
bool use_external_mp;
bool use_external_mp_once; // just for the next travel move
// this flag disables avoid_crossing_perimeters just for the next travel move
// we enable it by default for the first travel move in print
bool disable_once;
AvoidCrossingPerimeters() : use_external_mp(false), use_external_mp_once(false), disable_once(true) {}
~AvoidCrossingPerimeters() {}
void reset() { m_external_mp.reset(); m_layer_mp.reset(); }
void init_external_mp(const Print &print);
void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique<MotionPlanner>(islands); }
Polyline travel_to(const GCode &gcodegen, const Point &point);
private:
// For initializing the regions to avoid.
static Polygons collect_contours_all_layers(const PrintObjectPtrs& objects);
std::unique_ptr<MotionPlanner> m_external_mp;
std::unique_ptr<MotionPlanner> m_layer_mp;
};
class OozePrevention {
public:
bool enable;
@ -190,6 +161,7 @@ public:
const FullPrintConfig &config() const { return m_config; }
const Layer* layer() const { return m_layer; }
GCodeWriter& writer() { return m_writer; }
const GCodeWriter& writer() const { return m_writer; }
PlaceholderParser& placeholder_parser() { return m_placeholder_parser; }
const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; }
// Process a template through the placeholder parser, collect error messages to be reported

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,70 @@
#ifndef slic3r_AvoidCrossingPerimeters_hpp_
#define slic3r_AvoidCrossingPerimeters_hpp_
#include "../libslic3r.h"
#include "../ExPolygon.hpp"
#include "../EdgeGrid.hpp"
namespace Slic3r {
// Forward declarations.
class GCode;
class Layer;
class Point;
class AvoidCrossingPerimeters
{
public:
// Routing around the objects vs. inside a single object.
void use_external_mp(bool use = true) { m_use_external_mp = use; };
void use_external_mp_once() { m_use_external_mp_once = true; }
void disable_once() { m_disabled_once = true; }
bool disabled_once() const { return m_disabled_once; }
void reset_once_modifiers() { m_use_external_mp_once = false; m_disabled_once = false; }
void init_layer(const Layer &layer);
Polyline travel_to(const GCode& gcodegen, const Point& point)
{
bool could_be_wipe_disabled;
return this->travel_to(gcodegen, point, &could_be_wipe_disabled);
}
Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled);
struct Boundary {
// Collection of boundaries used for detection of crossing perimeters for travels
Polygons boundaries;
// Bounding box of boundaries
BoundingBoxf bbox;
// Precomputed distances of all points in boundaries
std::vector<std::vector<float>> boundaries_params;
// Used for detection of intersection between line and any polygon from boundaries
EdgeGrid::Grid grid;
void clear()
{
boundaries.clear();
boundaries_params.clear();
}
};
private:
bool m_use_external_mp { false };
// just for the next travel move
bool m_use_external_mp_once { false };
// this flag disables avoid_crossing_perimeters just for the next travel move
// we enable it by default for the first travel move in print
bool m_disabled_once { true };
// Used for detection of line or polyline is inside of any polygon.
EdgeGrid::Grid m_grid_lslice;
// Store all needed data for travels inside object
Boundary m_internal;
// Store all needed data for travels outside object
Boundary m_external;
};
} // namespace Slic3r
#endif // slic3r_AvoidCrossingPerimeters_hpp_

View file

@ -179,17 +179,22 @@ static int run_script(const std::string &script, const std::string &gcode, std::
namespace Slic3r {
void run_post_process_scripts(const std::string &path, const PrintConfig &config)
void run_post_process_scripts(const std::string &path, const DynamicPrintConfig &config)
{
if (config.post_process.values.empty())
const auto* post_process = config.opt<ConfigOptionStrings>("post_process");
if (// likely running in SLA mode
post_process == nullptr ||
// no post-processing script
post_process->values.empty())
return;
// Store print configuration into environment variables.
config.setenv_();
auto gcode_file = boost::filesystem::path(path);
if (! boost::filesystem::exists(gcode_file))
throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file"));
for (const std::string &scripts : config.post_process.values) {
for (const std::string &scripts : post_process->values) {
std::vector<std::string> lines;
boost::split(lines, scripts, boost::is_any_of("\r\n"));
for (std::string script : lines) {

View file

@ -8,7 +8,7 @@
namespace Slic3r {
extern void run_post_process_scripts(const std::string &path, const PrintConfig &config);
extern void run_post_process_scripts(const std::string &path, const DynamicPrintConfig &config);
} // namespace Slic3r

View file

@ -1,363 +0,0 @@
#include "BoundingBox.hpp"
#include "MotionPlanner.hpp"
#include "MutablePriorityQueue.hpp"
#include "Utils.hpp"
#include <limits> // for numeric_limits
#include <assert.h>
#define BOOST_VORONOI_USE_GMP 1
#include "boost/polygon/voronoi.hpp"
using boost::polygon::voronoi_builder;
using boost::polygon::voronoi_diagram;
namespace Slic3r {
MotionPlanner::MotionPlanner(const ExPolygons &islands) : m_initialized(false)
{
ExPolygons expp;
for (const ExPolygon &island : islands) {
island.simplify(SCALED_EPSILON, &expp);
for (ExPolygon &island : expp)
m_islands.emplace_back(MotionPlannerEnv(island));
expp.clear();
}
}
void MotionPlanner::initialize()
{
// prevent initialization of empty BoundingBox
if (m_initialized || m_islands.empty())
return;
// loop through islands in order to create inner expolygons and collect their contours.
Polygons outer_holes;
for (MotionPlannerEnv &island : m_islands) {
// Generate the internal env boundaries by shrinking the island
// we'll use these inner rings for motion planning (endpoints of the Voronoi-based
// graph, visibility check) in order to avoid moving too close to the boundaries.
island.m_env = ExPolygonCollection(offset_ex(island.m_island, -MP_INNER_MARGIN));
// Island contours are holes of our external environment.
outer_holes.push_back(island.m_island.contour);
}
// Generate a box contour around everyting.
Polygons contour = offset(get_extents(outer_holes).polygon(), +MP_OUTER_MARGIN*2);
assert(contour.size() == 1);
// make expolygon for outer environment
ExPolygons outer = diff_ex(contour, outer_holes);
assert(outer.size() == 1);
// If some of the islands are nested, then the 0th contour is the outer contour due to the order of conversion
// from Clipper data structure into the Slic3r expolygons inside diff_ex().
m_outer = MotionPlannerEnv(outer.front());
m_outer.m_env = ExPolygonCollection(diff_ex(contour, offset(outer_holes, +MP_OUTER_MARGIN)));
m_graphs.resize(m_islands.size() + 1);
m_initialized = true;
}
Polyline MotionPlanner::shortest_path(const Point &from, const Point &to)
{
// If we have an empty configuration space, return a straight move.
if (m_islands.empty())
return Polyline(from, to);
// Are both points in the same island?
int island_idx_from = -1;
int island_idx_to = -1;
int island_idx = -1;
for (MotionPlannerEnv &island : m_islands) {
int idx = &island - m_islands.data();
if (island.island_contains(from))
island_idx_from = idx;
if (island.island_contains(to))
island_idx_to = idx;
if (island_idx_from == idx && island_idx_to == idx) {
// Since both points are in the same island, is a direct move possible?
// If so, we avoid generating the visibility environment.
if (island.m_island.contains(Line(from, to)))
return Polyline(from, to);
// Both points are inside a single island, but the straight line crosses the island boundary.
island_idx = idx;
break;
}
}
// lazy generation of configuration space.
this->initialize();
// Get environment. If the from / to points do not share an island, then they cross an open space,
// therefore island_idx == -1 and env will be set to the environment of the empty space.
const MotionPlannerEnv &env = this->get_env(island_idx);
if (env.m_env.expolygons.empty()) {
// if this environment is empty (probably because it's too small), perform straight move
// and avoid running the algorithms on empty dataset
return Polyline(from, to);
}
// Now check whether points are inside the environment.
Point inner_from = from;
Point inner_to = to;
if (island_idx == -1) {
// The end points do not share the same island. In that case some of the travel
// will be likely performed inside the empty space.
// TODO: instead of using the nearest_env_point() logic, we should
// create a temporary graph where we connect 'from' and 'to' to the
// nodes which don't require more than one crossing, and let Dijkstra
// figure out the entire path - this should also replace the call to
// find_node() below
if (island_idx_from != -1)
// The start point is inside some island. Find the closest point at the empty space to start from.
inner_from = env.nearest_env_point(from, to);
if (island_idx_to != -1)
// The start point is inside some island. Find the closest point at the empty space to start from.
inner_to = env.nearest_env_point(to, inner_from);
}
// Perform a path search either in the open space, or in a common island of from/to.
const MotionPlannerGraph &graph = this->init_graph(island_idx);
// If no path exists without crossing perimeters, returns a straight segment.
Polyline polyline = graph.shortest_path(inner_from, inner_to);
polyline.points.insert(polyline.points.begin(), from);
polyline.points.emplace_back(to);
{
// grow our environment slightly in order for simplify_by_visibility()
// to work best by considering moves on boundaries valid as well
ExPolygonCollection grown_env(offset_ex(env.m_env.expolygons, float(+SCALED_EPSILON)));
if (island_idx == -1) {
/* If 'from' or 'to' are not inside our env, they were connected using the
nearest_env_point() search which maybe produce ugly paths since it does not
include the endpoint in the Dijkstra search; the simplify_by_visibility()
call below will not work in many cases where the endpoint is not contained in
grown_env (whose contour was arbitrarily constructed with MP_OUTER_MARGIN,
which may not be enough for, say, including a skirt point). So we prune
the extra points manually. */
if (! grown_env.contains(from)) {
// delete second point while the line connecting first to third crosses the
// boundaries as many times as the current first to second
while (polyline.points.size() > 2 && intersection_ln(Line(from, polyline.points[2]), (Polygons)grown_env).size() == 1)
polyline.points.erase(polyline.points.begin() + 1);
}
if (! grown_env.contains(to))
while (polyline.points.size() > 2 && intersection_ln(Line(*(polyline.points.end() - 3), to), (Polygons)grown_env).size() == 1)
polyline.points.erase(polyline.points.end() - 2);
}
// Perform some quick simplification (simplify_by_visibility() would make this
// unnecessary, but this is much faster)
polyline.simplify(MP_INNER_MARGIN/10);
// remove unnecessary vertices
// Note: this is computationally intensive and does not look very necessary
// now that we prune the endpoints with the logic above,
// so we comment it for now until a good test case arises
//polyline.simplify_by_visibility(grown_env);
/*
SVG svg("shortest_path.svg");
svg.draw(grown_env.expolygons);
svg.arrows = false;
for (MotionPlannerGraph::adjacency_list_t::const_iterator it = graph->adjacency_list.begin(); it != graph->adjacency_list.end(); ++it) {
Point a = graph->nodes[it - graph->adjacency_list.begin()];
for (std::vector<MotionPlannerGraph::Neighbor>::const_iterator n = it->begin(); n != it->end(); ++n) {
Point b = graph->nodes[n->target];
svg.draw(Line(a, b));
}
}
svg.arrows = true;
svg.draw(from);
svg.draw(inner_from, "red");
svg.draw(to);
svg.draw(inner_to, "red");
svg.draw(polyline, "red");
svg.Close();
*/
}
return polyline;
}
const MotionPlannerGraph& MotionPlanner::init_graph(int island_idx)
{
// 0th graph is the graph for m_outer. Other graphs are 1 indexed.
MotionPlannerGraph *graph = m_graphs[island_idx + 1].get();
if (graph == nullptr) {
// If this graph doesn't exist, initialize it.
m_graphs[island_idx + 1] = make_unique<MotionPlannerGraph>();
graph = m_graphs[island_idx + 1].get();
/* We don't add polygon boundaries as graph edges, because we'd need to connect
them to the Voronoi-generated edges by recognizing coinciding nodes. */
typedef voronoi_diagram<double> VD;
VD vd;
// Mapping between Voronoi vertices and graph nodes.
std::map<const VD::vertex_type*, size_t> vd_vertices;
// get boundaries as lines
const MotionPlannerEnv &env = this->get_env(island_idx);
Lines lines = env.m_env.lines();
boost::polygon::construct_voronoi(lines.begin(), lines.end(), &vd);
// traverse the Voronoi diagram and generate graph nodes and edges
for (const VD::edge_type &edge : vd.edges()) {
if (edge.is_infinite())
continue;
const VD::vertex_type* v0 = edge.vertex0();
const VD::vertex_type* v1 = edge.vertex1();
Point p0(v0->x(), v0->y());
Point p1(v1->x(), v1->y());
// Insert only Voronoi edges fully contained in the island.
//FIXME This test has a terrible O(n^2) time complexity.
if (env.island_contains_b(p0) && env.island_contains_b(p1)) {
// Find v0 in the graph, allocate a new node if v0 does not exist in the graph yet.
auto i_v0 = vd_vertices.find(v0);
size_t v0_idx;
if (i_v0 == vd_vertices.end())
vd_vertices[v0] = v0_idx = graph->add_node(p0);
else
v0_idx = i_v0->second;
// Find v1 in the graph, allocate a new node if v0 does not exist in the graph yet.
auto i_v1 = vd_vertices.find(v1);
size_t v1_idx;
if (i_v1 == vd_vertices.end())
vd_vertices[v1] = v1_idx = graph->add_node(p1);
else
v1_idx = i_v1->second;
// Euclidean distance is used as weight for the graph edge
graph->add_edge(v0_idx, v1_idx, (p1 - p0).cast<double>().norm());
}
}
}
return *graph;
}
// Find a middle point on the path from start_point to end_point with the shortest path.
static inline size_t nearest_waypoint_index(const Point &start_point, const Points &middle_points, const Point &end_point)
{
size_t idx = size_t(-1);
double dmin = std::numeric_limits<double>::infinity();
for (const Point &p : middle_points) {
double d = (p - start_point).cast<double>().norm() + (end_point - p).cast<double>().norm();
if (d < dmin) {
idx = &p - middle_points.data();
dmin = d;
if (dmin < EPSILON)
break;
}
}
return idx;
}
Point MotionPlannerEnv::nearest_env_point(const Point &from, const Point &to) const
{
/* In order to ensure that the move between 'from' and the initial env point does
not violate any of the configuration space boundaries, we limit our search to
the points that satisfy this condition. */
/* Assume that this method is never called when 'env' contains 'from';
so 'from' is either inside a hole or outside all contours */
// get the points of the hole containing 'from', if any
Points pp;
for (const ExPolygon &ex : m_env.expolygons) {
for (const Polygon &hole : ex.holes)
if (hole.contains(from))
pp = hole.points;
if (! pp.empty())
break;
}
// If 'from' is not inside a hole, it's outside of all contours, so take all contours' points.
if (pp.empty())
for (const ExPolygon &ex : m_env.expolygons)
append(pp, ex.contour.points);
// Find the candidate result and check that it doesn't cross too many boundaries.
while (pp.size() > 1) {
// find the point in pp that is closest to both 'from' and 'to'
size_t result = nearest_waypoint_index(from, pp, to);
// as we assume 'from' is outside env, any node will require at least one crossing
if (intersection_ln(Line(from, pp[result]), m_island).size() > 1) {
// discard result
pp.erase(pp.begin() + result);
} else
return pp[result];
}
// if we're here, return last point if any (better than nothing)
// if we have no points at all, then we have an empty environment and we
// make this method behave as a no-op (we shouldn't get here by the way)
return pp.empty() ? from : pp.front();
}
// Add a new directed edge to the adjacency graph.
void MotionPlannerGraph::add_edge(size_t from, size_t to, double weight)
{
// Extend adjacency list until this start node.
if (m_adjacency_list.size() < from + 1) {
// Reserve in powers of two to avoid repeated reallocation.
m_adjacency_list.reserve(std::max<uint32_t>(8, next_highest_power_of_2((uint32_t)(from + 1))));
// Allocate new empty adjacency vectors.
m_adjacency_list.resize(from + 1);
}
m_adjacency_list[from].emplace_back(Neighbor(node_t(to), weight));
}
// Dijkstra's shortest path in a weighted graph from node_start to node_end.
// The returned path contains the end points.
// If no path exists from node_start to node_end, a straight segment is returned.
Polyline MotionPlannerGraph::shortest_path(size_t node_start, size_t node_end) const
{
// This prevents a crash in case for some reason we got here with an empty adjacency list.
if (this->empty())
return Polyline();
// Dijkstra algorithm, previous node of the current node 'u' in the shortest path towards node_start.
std::vector<node_t> previous(m_adjacency_list.size(), -1);
std::vector<weight_t> distance(m_adjacency_list.size(), std::numeric_limits<weight_t>::infinity());
std::vector<size_t> map_node_to_queue_id(m_adjacency_list.size(), size_t(-1));
distance[node_start] = 0.;
auto queue = make_mutable_priority_queue<node_t, false>(
[&map_node_to_queue_id](const node_t node, size_t idx) { map_node_to_queue_id[node] = idx; },
[&distance](const node_t node1, const node_t node2) { return distance[node1] < distance[node2]; });
queue.reserve(m_adjacency_list.size());
for (size_t i = 0; i < m_adjacency_list.size(); ++ i)
queue.push(node_t(i));
while (! queue.empty()) {
// Get the next node with the lowest distance to node_start.
node_t u = node_t(queue.top());
queue.pop();
map_node_to_queue_id[u] = size_t(-1);
// Stop searching if we reached our destination.
if (size_t(u) == node_end)
break;
// Visit each edge starting at node u.
for (const Neighbor& neighbor : m_adjacency_list[u])
if (map_node_to_queue_id[neighbor.target] != size_t(-1)) {
weight_t alt = distance[u] + neighbor.weight;
// If total distance through u is shorter than the previous
// distance (if any) between node_start and neighbor.target, replace it.
if (alt < distance[neighbor.target]) {
distance[neighbor.target] = alt;
previous[neighbor.target] = u;
queue.update(map_node_to_queue_id[neighbor.target]);
}
}
}
// In case the end point was not reached, previous[node_end] contains -1
// and a straight line from node_start to node_end is returned.
Polyline polyline;
polyline.points.reserve(m_adjacency_list.size());
for (node_t vertex = node_t(node_end); vertex != -1; vertex = previous[vertex])
polyline.points.emplace_back(m_nodes[vertex]);
polyline.points.emplace_back(m_nodes[node_start]);
polyline.reverse();
return polyline;
}
}

View file

@ -1,91 +0,0 @@
#ifndef slic3r_MotionPlanner_hpp_
#define slic3r_MotionPlanner_hpp_
#include "libslic3r.h"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "ExPolygonCollection.hpp"
#include "Polyline.hpp"
#include <map>
#include <utility>
#include <memory>
#include <vector>
#define MP_INNER_MARGIN scale_(1.0)
#define MP_OUTER_MARGIN scale_(2.0)
namespace Slic3r {
class MotionPlanner;
class MotionPlannerEnv
{
friend class MotionPlanner;
public:
MotionPlannerEnv() {};
MotionPlannerEnv(const ExPolygon &island) : m_island(island), m_island_bbox(get_extents(island)) {};
Point nearest_env_point(const Point &from, const Point &to) const;
bool island_contains(const Point &pt) const
{ return m_island_bbox.contains(pt) && m_island.contains(pt); }
bool island_contains_b(const Point &pt) const
{ return m_island_bbox.contains(pt) && m_island.contains_b(pt); }
private:
ExPolygon m_island;
BoundingBox m_island_bbox;
// Region, where the travel is allowed.
ExPolygonCollection m_env;
};
// A 2D directed graph for searching a shortest path using the famous Dijkstra algorithm.
class MotionPlannerGraph
{
public:
// Add a directed edge into the graph.
size_t add_node(const Point &p) { m_nodes.emplace_back(p); return m_nodes.size() - 1; }
void add_edge(size_t from, size_t to, double weight);
size_t find_closest_node(const Point &point) const { return point.nearest_point_index(m_nodes); }
bool empty() const { return m_adjacency_list.empty(); }
Polyline shortest_path(size_t from, size_t to) const;
Polyline shortest_path(const Point &from, const Point &to) const
{ return this->shortest_path(this->find_closest_node(from), this->find_closest_node(to)); }
private:
typedef int node_t;
typedef double weight_t;
struct Neighbor {
Neighbor(node_t target, weight_t weight) : target(target), weight(weight) {}
node_t target;
weight_t weight;
};
Points m_nodes;
std::vector<std::vector<Neighbor>> m_adjacency_list;
};
class MotionPlanner
{
public:
MotionPlanner(const ExPolygons &islands);
~MotionPlanner() {}
Polyline shortest_path(const Point &from, const Point &to);
size_t islands_count() const { return m_islands.size(); }
private:
bool m_initialized;
std::vector<MotionPlannerEnv> m_islands;
MotionPlannerEnv m_outer;
// 0th graph is the graph for m_outer. Other graphs are 1 indexed.
std::vector<std::unique_ptr<MotionPlannerGraph>> m_graphs;
void initialize();
const MotionPlannerGraph& init_graph(int island_idx);
const MotionPlannerEnv& get_env(int island_idx) const
{ return (island_idx == -1) ? m_outer : m_islands[island_idx]; }
};
}
#endif

View file

@ -18,13 +18,6 @@ void MultiPoint::scale(double factor_x, double factor_y)
}
}
void MultiPoint::translate(double x, double y)
{
Vector v(x, y);
for (Point &pt : points)
pt += v;
}
void MultiPoint::translate(const Point &v)
{
for (Point &pt : points)
@ -140,6 +133,17 @@ bool MultiPoint::first_intersection(const Line& line, Point* intersection) const
return found;
}
bool MultiPoint::intersections(const Line &line, Points *intersections) const
{
size_t intersections_size = intersections->size();
for (const Line &polygon_line : this->lines()) {
Point intersection;
if (polygon_line.intersection(line, &intersection))
intersections->emplace_back(std::move(intersection));
}
return intersections->size() > intersections_size;
}
std::vector<Point> MultiPoint::_douglas_peucker(const std::vector<Point>& pts, const double tolerance)
{
std::vector<Point> result_pts;

View file

@ -26,7 +26,7 @@ public:
MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; }
void scale(double factor);
void scale(double factor_x, double factor_y);
void translate(double x, double y);
void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); }
void translate(const Point &vector);
void rotate(double angle) { this->rotate(cos(angle), sin(angle)); }
void rotate(double cos_angle, double sin_angle);
@ -79,7 +79,8 @@ public:
bool intersection(const Line& line, Point* intersection) const;
bool first_intersection(const Line& line, Point* intersection) const;
bool intersections(const Line &line, Points *intersections) const;
static Points _douglas_peucker(const Points &points, const double tolerance);
static Points visivalingam(const Points& pts, const double& tolerance);
};

View file

@ -410,7 +410,7 @@ const std::vector<std::string>& Preset::print_options()
"infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle",
"solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first",
"ironing", "ironing_type", "ironing_flowrate", "ironing_speed", "ironing_spacing",
"max_print_speed", "max_volumetric_speed",
"max_print_speed", "max_volumetric_speed", "avoid_crossing_perimeters_max_detour",
#ifdef HAS_PRESSURE_EQUALIZER
"max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative",
#endif /* HAS_PRESSURE_EQUALIZER */
@ -991,7 +991,15 @@ const Preset* PresetCollection::get_preset_parent(const Preset& child) const
if (it != m_presets.end())
preset = &(*it);
}
return (preset == nullptr/* || preset->is_default */|| preset->is_external) ? nullptr : preset;
return
// not found
(preset == nullptr/* || preset->is_default */||
// this should not happen, user profile should not derive from an external profile
preset->is_external ||
// this should not happen, however people are creative, see GH #4996
preset == &child) ?
nullptr :
preset;
}
// Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist.

View file

@ -4,6 +4,7 @@
#include "libslic3r.h"
#include "Utils.hpp"
#include "Model.hpp"
#include "format.hpp"
#include <algorithm>
#include <set>
@ -1095,7 +1096,11 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
namespace pt = boost::property_tree;
pt::ptree tree;
boost::nowide::ifstream ifs(path);
pt::read_ini(ifs, tree);
try {
pt::read_ini(ifs, tree);
} catch (const boost::property_tree::ini_parser::ini_parser_error &err) {
throw Slic3r::RuntimeError(format("Failed loading config bundle \"%1%\"\nError: \"%2%\" at line %3%", path, err.message(), err.line()).c_str());
}
const VendorProfile *vendor_profile = nullptr;
if (flags & (LOAD_CFGBNDLE_SYSTEM | LOAD_CFGBUNDLE_VENDOR_ONLY)) {

View file

@ -71,6 +71,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
// or they are only notes not influencing the generated G-code.
static std::unordered_set<std::string> steps_gcode = {
"avoid_crossing_perimeters",
"avoid_crossing_perimeters_max_detour",
"bed_shape",
"bed_temperature",
"before_layer_gcode",

View file

@ -63,8 +63,10 @@ void PrintConfigDef::init_common_params()
def->set_default_value(new ConfigOptionString(""));
def = this->add("thumbnails", coPoints);
def->label = L("Picture sizes to be stored into a .gcode and .sl1 files");
def->label = L("G-code thumbnails");
def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 files");
def->mode = comExpert;
def->gui_type = "one_string";
def->set_default_value(new ConfigOptionPoints());
def = this->add("layer_height", coFloat);
@ -182,6 +184,16 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("avoid_crossing_perimeters_max_detour", coFloat);
def->label = L("Avoid crossing perimeters - The max detour lenght");
def->category = L("Layers and Perimeters");
def->tooltip = L("The maximum detour length for avoid crossing perimeters. "
"If the detour is longer than this value, avoid crossing perimeters is not applied for this path.");
def->sidetext = L("mm (zero to disable)");
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.));
def = this->add("bed_temperature", coInts);
def->label = L("Other layers");
def->tooltip = L("Bed temperature for layers after the first one. "
@ -1981,7 +1993,7 @@ void PrintConfigDef::init_fff_params()
"in order to remove any visible seam. This option requires a single perimeter, "
"no infill, no top solid layers and no support material. You can still set "
"any number of bottom solid layers as well as skirt/brim loops. "
"It won't work when printing more than an object.");
"It won't work when printing more than one single object.");
def->set_default_value(new ConfigOptionBool(false));
def = this->add("standby_temperature_delta", coInt);

View file

@ -828,6 +828,7 @@ class PrintConfig : public MachineEnvelopeConfig, public GCodeConfig
public:
ConfigOptionBool avoid_crossing_perimeters;
ConfigOptionFloat avoid_crossing_perimeters_max_detour;
ConfigOptionPoints bed_shape;
ConfigOptionInts bed_temperature;
ConfigOptionFloat bridge_acceleration;
@ -902,6 +903,7 @@ protected:
this->MachineEnvelopeConfig::initialize(cache, base_ptr);
this->GCodeConfig::initialize(cache, base_ptr);
OPT_PTR(avoid_crossing_perimeters);
OPT_PTR(avoid_crossing_perimeters_max_detour);
OPT_PTR(bed_shape);
OPT_PTR(bed_temperature);
OPT_PTR(bridge_acceleration);

View file

@ -184,7 +184,7 @@ inline std::unique_ptr<T> make_unique(Args&&... args) {
// Variant of std::lower_bound() with compare predicate, but without the key.
// This variant is very useful in case that the T type is large or it does not even have a public constructor.
template<class ForwardIt, class LowerThanKeyPredicate>
ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_thank_key)
ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_than_key)
{
ForwardIt it;
typename std::iterator_traits<ForwardIt>::difference_type count, step;
@ -194,7 +194,7 @@ ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKey
it = first;
step = count / 2;
std::advance(it, step);
if (lower_thank_key(*it)) {
if (lower_than_key(*it)) {
first = ++it;
count -= step + 1;
}

View file

@ -264,12 +264,14 @@ bool Snapshot::equal_to_active(const AppConfig &app_config) const
boost::filesystem::path path1 = data_dir / subdir;
boost::filesystem::path path2 = snapshot_dir / subdir;
std::vector<std::string> files1, files2;
for (auto &dir_entry : boost::filesystem::directory_iterator(path1))
if (Slic3r::is_ini_file(dir_entry))
files1.emplace_back(dir_entry.path().filename().string());
for (auto &dir_entry : boost::filesystem::directory_iterator(path2))
if (Slic3r::is_ini_file(dir_entry))
files2.emplace_back(dir_entry.path().filename().string());
if (boost::filesystem::is_directory(path1))
for (auto &dir_entry : boost::filesystem::directory_iterator(path1))
if (Slic3r::is_ini_file(dir_entry))
files1.emplace_back(dir_entry.path().filename().string());
if (boost::filesystem::is_directory(path2))
for (auto &dir_entry : boost::filesystem::directory_iterator(path2))
if (Slic3r::is_ini_file(dir_entry))
files2.emplace_back(dir_entry.path().filename().string());
std::sort(files1.begin(), files1.end());
std::sort(files2.begin(), files2.end());
if (files1 != files2)
@ -459,8 +461,11 @@ void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_confi
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
// Remove existing ini files and restore the ini files from the snapshot.
for (const char *subdir : snapshot_subdirs) {
delete_existing_ini_files(data_dir / subdir);
copy_config_dir_single_level(snapshot_dir / subdir, data_dir / subdir);
boost::filesystem::path src = snapshot_dir / subdir;
boost::filesystem::path dst = data_dir / subdir;
delete_existing_ini_files(dst);
if (boost::filesystem::is_directory(src))
copy_config_dir_single_level(src, dst);
}
// Update AppConfig with the selections of the print / sla_print / filament / sla_material / printer profiles
// and about the installed printer types and variants.

View file

@ -7,6 +7,8 @@
#include "MainFrame.hpp"
#include "format.hpp"
#include <wx/clipbrd.h>
namespace Slic3r {
namespace GUI {
@ -297,6 +299,11 @@ AboutDialog::AboutDialog()
auto copy_rights_btn = new wxButton(this, m_copy_rights_btn_id, _L("Portions copyright")+dots);
buttons->Insert(0, copy_rights_btn, 0, wxLEFT, 5);
copy_rights_btn->Bind(wxEVT_BUTTON, &AboutDialog::onCopyrightBtn, this);
m_copy_version_btn_id = NewControlId();
auto copy_version_btn = new wxButton(this, m_copy_version_btn_id, _L("Copy Version Info"));
buttons->Insert(1, copy_version_btn, 0, wxLEFT, 5);
copy_version_btn->Bind(wxEVT_BUTTON, &AboutDialog::onCopyToClipboard, this);
this->SetEscapeId(wxID_CLOSE);
this->Bind(wxEVT_BUTTON, &AboutDialog::onCloseDialog, this, wxID_CLOSE);
@ -348,5 +355,12 @@ void AboutDialog::onCopyrightBtn(wxEvent &)
dlg.ShowModal();
}
void AboutDialog::onCopyToClipboard(wxEvent&)
{
wxTheClipboard->Open();
wxTheClipboard->SetData(new wxTextDataObject(_L("Version") + " " + std::string(SLIC3R_VERSION)));
wxTheClipboard->Close();
}
} // namespace GUI
} // namespace Slic3r

View file

@ -60,6 +60,7 @@ class AboutDialog : public DPIDialog
wxHtmlWindow* m_html;
wxStaticBitmap* m_logo;
int m_copy_rights_btn_id { wxID_ANY };
int m_copy_version_btn_id { wxID_ANY };
public:
AboutDialog();
@ -70,6 +71,7 @@ private:
void onLinkClicked(wxHtmlLinkEvent &event);
void onCloseDialog(wxEvent &);
void onCopyrightBtn(wxEvent &);
void onCopyToClipboard(wxEvent&);
};
} // namespace GUI

View file

@ -185,7 +185,7 @@ void BackgroundSlicingProcess::process_fff()
break;
}
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
run_post_process_scripts(export_path, m_fff_print->config());
run_post_process_scripts(export_path, m_fff_print->full_print_config());
m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str());
} else if (! m_upload_job.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
@ -538,7 +538,7 @@ void BackgroundSlicingProcess::prepare_upload()
if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS) {
throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed")));
}
run_post_process_scripts(source_path.string(), m_fff_print->config());
run_post_process_scripts(source_path.string(), m_fff_print->full_print_config());
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
} else {
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());

View file

@ -2,6 +2,7 @@
#include "ConfigManipulation.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
#include "format.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp"
@ -184,30 +185,21 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
}
if (config->option<ConfigOptionPercent>("fill_density")->value == 100) {
auto fill_pattern = config->option<ConfigOptionEnum<InfillPattern>>("fill_pattern")->value;
std::string str_fill_pattern = "";
t_config_enum_values map_names = config->option<ConfigOptionEnum<InfillPattern>>("fill_pattern")->get_enum_values();
for (auto it : map_names) {
if (fill_pattern == it.second) {
str_fill_pattern = it.first;
break;
}
}
if (!str_fill_pattern.empty()) {
const std::vector<std::string>& external_fill_pattern = config->def()->get("top_fill_pattern")->enum_values;
bool correct_100p_fill = false;
for (const std::string& fill : external_fill_pattern)
{
if (str_fill_pattern == fill)
correct_100p_fill = true;
}
std::string fill_pattern = config->option<ConfigOptionEnum<InfillPattern>>("fill_pattern")->serialize();
const auto &top_fill_pattern_values = config->def()->get("top_fill_pattern")->enum_values;
bool correct_100p_fill = std::find(top_fill_pattern_values.begin(), top_fill_pattern_values.end(), fill_pattern) != top_fill_pattern_values.end();
if (!correct_100p_fill) {
// get fill_pattern name from enum_labels for using this one at dialog_msg
str_fill_pattern = _utf8(config->def()->get("fill_pattern")->enum_labels[fill_pattern]);
if (!correct_100p_fill) {
wxString msg_text = GUI::from_u8((boost::format(_utf8(L("The %1% infill pattern is not supposed to work at 100%% density."))) % str_fill_pattern).str());
const ConfigOptionDef *fill_pattern_def = config->def()->get("fill_pattern");
assert(fill_pattern_def != nullptr);
auto it_pattern = std::find(fill_pattern_def->enum_values.begin(), fill_pattern_def->enum_values.end(), fill_pattern);
assert(it_pattern != fill_pattern_def->enum_values.end());
if (it_pattern != fill_pattern_def->enum_values.end()) {
wxString msg_text = GUI::format_wxstr(_L("The %1% infill pattern is not supposed to work at 100%% density."),
_(fill_pattern_def->enum_labels[it_pattern - fill_pattern_def->enum_values.begin()]));
if (is_global_config)
msg_text += "\n\n" + _(L("Shall I switch to rectilinear fill pattern?"));
wxMessageDialog dialog(nullptr, msg_text, _(L("Infill")),
msg_text += "\n\n" + _L("Shall I switch to rectilinear fill pattern?");
wxMessageDialog dialog(nullptr, msg_text, _L("Infill"),
wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK) );
DynamicPrintConfig new_conf = *config;
auto answer = dialog.ShowModal();
@ -315,6 +307,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle",
"wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" })
toggle_field(el, have_wipe_tower);
bool have_avoid_crossing_perimeters = config->opt_bool("avoid_crossing_perimeters");
toggle_field("avoid_crossing_perimeters_max_detour", have_avoid_crossing_perimeters);
}
void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/)

View file

@ -5,6 +5,7 @@
#include "wxExtensions.hpp"
#include "Plater.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
#include "libslic3r/PrintConfig.hpp"
@ -12,6 +13,7 @@
#include <wx/numformatter.h>
#include <wx/tooltip.h>
#include <wx/notebook.h>
#include <wx/tokenzr.h>
#include <boost/algorithm/string/predicate.hpp>
#include "OG_CustomCtrl.hpp"
@ -52,6 +54,16 @@ wxString double_to_string(double const value, const int max_precision /*= 4*/)
return s;
}
wxString get_thumbnails_string(const std::vector<Vec2d>& values)
{
wxString ret_str;
if (!values.empty())
for (auto el : values)
ret_str += wxString::Format("%ix%i, ", int(el[0]), int(el[1]));
return ret_str;
}
Field::~Field()
{
if (m_on_kill_focus)
@ -304,6 +316,56 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true
m_value = std::string(str.ToUTF8().data());
break; }
case coPoints: {
std::vector<Vec2d> out_values;
str.Replace(" ", wxEmptyString, true);
if (!str.IsEmpty()) {
bool invalid_val = false;
bool out_of_range_val = false;
wxStringTokenizer thumbnails(str, ",");
while (thumbnails.HasMoreTokens()) {
wxString token = thumbnails.GetNextToken();
double x, y;
wxStringTokenizer thumbnail(token, "x");
if (thumbnail.HasMoreTokens()) {
wxString x_str = thumbnail.GetNextToken();
if (x_str.ToDouble(&x) && thumbnail.HasMoreTokens()) {
wxString y_str = thumbnail.GetNextToken();
if (y_str.ToDouble(&y) && !thumbnail.HasMoreTokens()) {
if (0 < x && x < 1000 && 0 < y && y < 1000) {
out_values.push_back(Vec2d(x, y));
continue;
}
out_of_range_val = true;
break;
}
}
}
invalid_val = true;
break;
}
if (out_of_range_val) {
wxString text_value;
if (!m_value.empty())
text_value = get_thumbnails_string(boost::any_cast<std::vector<Vec2d>>(m_value));
set_value(text_value, true);
show_error(m_parent, _L("Input value is out of range")
);
}
else if (invalid_val) {
wxString text_value;
if (!m_value.empty())
text_value = get_thumbnails_string(boost::any_cast<std::vector<Vec2d>>(m_value));
set_value(text_value, true);
show_error(m_parent, format_wxstr(_L("Invalid input format. It must be represented like \"%1%\""),"XxY, XxY, ..." ));
}
}
m_value = out_values;
break; }
default:
break;
}
@ -371,6 +433,9 @@ void TextCtrl::BUILD() {
text_value = vec->get_at(m_opt_idx);
break;
}
case coPoints:
text_value = get_thumbnails_string(m_opt.get_default_value<ConfigOptionPoints>()->values);
break;
default:
break;
}

View file

@ -37,6 +37,7 @@ using t_change = std::function<void(const t_config_option_key&, const boost::any
using t_back_to_init = std::function<void(const std::string&)>;
wxString double_to_string(double const value, const int max_precision = 4);
wxString get_thumbnails_string(const std::vector<Vec2d>& values);
class Field {
protected:

View file

@ -1679,22 +1679,20 @@ void GLCanvas3D::render()
if (wxGetApp().plater()->is_render_statistic_dialog_visible()) {
ImGuiWrapper& imgui = *wxGetApp().imgui();
imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
imgui.text("Last frame: ");
imgui.text("Last frame:");
ImGui::SameLine();
imgui.text(std::to_string(m_render_stats.last_frame));
long long average = m_render_stats.get_average();
imgui.text(std::to_string(average));
ImGui::SameLine();
imgui.text(" ms");
imgui.text("FPS: ");
imgui.text("ms");
imgui.text("FPS:");
ImGui::SameLine();
imgui.text(std::to_string(static_cast<int>(1000.0f / static_cast<float>(m_render_stats.last_frame))));
// imgui.text("Imgui FPS: ");
// ImGui::SameLine();
// imgui.text(std::to_string(static_cast<int>(ImGui::GetIO().Framerate)));
imgui.text(std::to_string((average == 0) ? 0 : static_cast<int>(1000.0f / static_cast<float>(average))));
ImGui::Separator();
imgui.text("Compressed textures: ");
imgui.text("Compressed textures:");
ImGui::SameLine();
imgui.text(OpenGLManager::are_compressed_textures_supported() ? "supported" : "not supported");
imgui.text("Max texture size: ");
imgui.text("Max texture size:");
ImGui::SameLine();
imgui.text(std::to_string(OpenGLManager::get_gl_info().get_max_tex_size()));
imgui.end();
@ -1707,8 +1705,6 @@ void GLCanvas3D::render()
std::string tooltip;
// Negative coordinate means out of the window, likely because the window was deactivated.
// In that case the tooltip should be hidden.
if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) {
@ -1745,7 +1741,7 @@ void GLCanvas3D::render()
#if ENABLE_RENDER_STATISTICS
auto end_time = std::chrono::high_resolution_clock::now();
m_render_stats.last_frame = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count();
m_render_stats.add_frame(std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count());
#endif // ENABLE_RENDER_STATISTICS
}
@ -2491,7 +2487,12 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
break;
#if ENABLE_CTRL_M_ON_WINDOWS
#ifdef __APPLE__
case 'm':
case 'M':
#else /* __APPLE__ */
case WXK_CONTROL_M:
#endif /* __APPLE__ */
{
#ifdef _WIN32
if (wxGetApp().app_config->get("use_legacy_3DConnexion") == "1") {
@ -3779,7 +3780,7 @@ GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const
m_config->opt_float("wipe_tower_y"));
wti.m_rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle");
const BoundingBoxf3& bb = vol->bounding_box();
wti.m_bb_size = Vec2d(bb.size().x(), bb.size().y());
wti.m_bb = BoundingBoxf{to_2d(bb.min), to_2d(bb.max)};
break;
}
}

View file

@ -320,11 +320,22 @@ class GLCanvas3D
};
#if ENABLE_RENDER_STATISTICS
struct RenderStats
class RenderStats
{
long long last_frame;
std::queue<std::pair<long long, long long>> m_frames;
long long m_curr_total{ 0 };
RenderStats() : last_frame(0) {}
public:
void add_frame(long long frame) {
long long now = wxGetLocalTimeMillis().GetValue();
if (!m_frames.empty() && now - m_frames.front().first > 1000) {
m_curr_total -= m_frames.front().second;
m_frames.pop();
}
m_curr_total += frame;
m_frames.push({ now, frame });
}
long long get_average() const { return m_frames.empty() ? 0 : m_curr_total / m_frames.size(); }
};
#endif // ENABLE_RENDER_STATISTICS
@ -665,8 +676,8 @@ public:
class WipeTowerInfo {
protected:
Vec2d m_pos = {std::nan(""), std::nan("")};
Vec2d m_bb_size = {0., 0.};
double m_rotation = 0.;
BoundingBoxf m_bb;
friend class GLCanvas3D;
public:
@ -677,7 +688,7 @@ public:
inline const Vec2d& pos() const { return m_pos; }
inline double rotation() const { return m_rotation; }
inline const Vec2d bb_size() const { return m_bb_size; }
inline const Vec2d bb_size() const { return m_bb.size(); }
void apply_wipe_tower() const;
};

View file

@ -201,7 +201,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
}
break;
case coPoints:{
if (opt_key.compare("bed_shape") == 0) {
if (opt_key == "bed_shape" || opt_key == "thumbnails") {
config.option<ConfigOptionPoints>(opt_key)->values = boost::any_cast<std::vector<Vec2d>>(value);
break;
}

View file

@ -933,13 +933,7 @@ bool GUI_App::on_init_inner()
load_current_presets();
mainframe->Show(true);
/* Temporary workaround for the correct behavior of the Scrolled sidebar panel:
* change min hight of object list to the normal min value (15 * wxGetApp().em_unit())
* after first whole Mainframe updating/layouting
*/
const int list_min_height = 15 * em_unit();
if (obj_list()->GetMinSize().GetY() > list_min_height)
obj_list()->SetMinSize(wxSize(-1, list_min_height));
obj_list()->set_min_height();
update_mode(); // update view mode after fix of the object_list size
@ -1150,13 +1144,8 @@ void GUI_App::recreate_GUI(const wxString& msg_name)
mainframe->Show(true);
dlg.Update(90, _L("Loading of a mode view") + dots);
/* Temporary workaround for the correct behavior of the Scrolled sidebar panel:
* change min hight of object list to the normal min value (15 * wxGetApp().em_unit())
* after first whole Mainframe updating/layouting
*/
const int list_min_height = 15 * em_unit();
if (obj_list()->GetMinSize().GetY() > list_min_height)
obj_list()->SetMinSize(wxSize(-1, list_min_height));
obj_list()->set_min_height();
update_mode();
// #ys_FIXME_delete_after_testing Do we still need this ?
@ -1582,13 +1571,16 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot);
dlg.ShowModal();
if (!dlg.snapshot_to_activate().empty()) {
if (!Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK);
app_config->set("on_snapshot",
Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id);
preset_bundle->load_presets(*app_config);
// Load the currently selected preset into the GUI, update the preset selection box.
load_current_presets();
try {
app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id);
preset_bundle->load_presets(*app_config);
// Load the currently selected preset into the GUI, update the preset selection box.
load_current_presets();
} catch (std::exception &ex) {
GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what()));
}
}
}
break;

View file

@ -191,7 +191,7 @@ ObjectList::ObjectList(wxWindow* parent) :
// Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this);
{
// Accelerators
wxAcceleratorEntry entries[8];
wxAcceleratorEntry entries[10];
entries[0].Set(wxACCEL_CTRL, (int) 'C', wxID_COPY);
entries[1].Set(wxACCEL_CTRL, (int) 'X', wxID_CUT);
entries[2].Set(wxACCEL_CTRL, (int) 'V', wxID_PASTE);
@ -200,7 +200,9 @@ ObjectList::ObjectList(wxWindow* parent) :
entries[5].Set(wxACCEL_CTRL, (int) 'Y', wxID_REDO);
entries[6].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_DELETE);
entries[7].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE);
wxAcceleratorTable accel(8, entries);
entries[8].Set(wxACCEL_NORMAL, int('+'), wxID_ADD);
entries[9].Set(wxACCEL_NORMAL, int('-'), wxID_REMOVE);
wxAcceleratorTable accel(10, entries);
SetAcceleratorTable(accel);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->copy(); }, wxID_COPY);
@ -209,6 +211,8 @@ ObjectList::ObjectList(wxWindow* parent) :
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove(); }, wxID_DELETE);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->undo(); }, wxID_UNDO);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->redo(); }, wxID_REDO);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->increase_instances(); }, wxID_ADD);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->decrease_instances(); }, wxID_REMOVE);
}
#else //__WXOSX__
Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX
@ -254,6 +258,18 @@ ObjectList::~ObjectList()
{
}
void ObjectList::set_min_height()
{
/* Temporary workaround for the correct behavior of the Scrolled sidebar panel:
* change min hight of object list to the normal min value (35 * wxGetApp().em_unit())
* after first whole Mainframe updating/layouting
*/
const int list_min_height = 35 * wxGetApp().em_unit();
if (this->GetMinSize().GetY() > list_min_height)
this->SetMinSize(wxSize(-1, list_min_height));
}
void ObjectList::create_objects_ctrl()
{
/* Temporary workaround for the correct behavior of the Scrolled sidebar panel:
@ -1092,6 +1108,16 @@ void ObjectList::redo()
wxGetApp().plater()->redo();
}
void ObjectList::increase_instances()
{
wxGetApp().plater()->increase_instances(1);
}
void ObjectList::decrease_instances()
{
wxGetApp().plater()->decrease_instances(1);
}
#ifndef __WXOSX__
void ObjectList::key_event(wxKeyEvent& event)
{
@ -1116,6 +1142,10 @@ void ObjectList::key_event(wxKeyEvent& event)
redo();
else if (wxGetKeyState(wxKeyCode('Z')) && wxGetKeyState(WXK_CONTROL))
undo();
else if (event.GetUnicodeKey() == '+')
increase_instances();
else if (event.GetUnicodeKey() == '-')
decrease_instances();
else
event.Skip();
}

View file

@ -207,6 +207,7 @@ public:
ObjectList(wxWindow* parent);
~ObjectList();
void set_min_height();
std::map<std::string, wxBitmap> CATEGORY_ICON;
@ -257,6 +258,8 @@ public:
bool paste_from_clipboard();
void undo();
void redo();
void increase_instances();
void decrease_instances();
void get_settings_choice(const wxString& category_name);
void get_freq_settings_choice(const wxString& bundle_name);

View file

@ -30,11 +30,10 @@ public:
ArrangePolygon get_arrange_polygon() const
{
Polygon ap({
{coord_t(0), coord_t(0)},
{scaled(m_bb_size(X)), coord_t(0)},
{scaled(m_bb_size)},
{coord_t(0), scaled(m_bb_size(Y))},
{coord_t(0), coord_t(0)},
{scaled(m_bb.min)},
{scaled(m_bb.max.x()), scaled(m_bb.min.y())},
{scaled(m_bb.max)},
{scaled(m_bb.min.x()), scaled(m_bb.max.y())}
});
ArrangePolygon ret;

View file

@ -132,6 +132,7 @@ void KBShortcutsDialog::fill_shortcuts()
{ ctrl + "5", L("Switch to 3D") },
{ ctrl + "6", L("Switch to Preview") },
{ ctrl + "J", L("Print host upload queue") },
{ ctrl + "Shift+" + "I", L("Open new instance") },
// View
{ "0-6", L("Camera view") },
{ "E", L("Show/Hide object/instance labels") },

View file

@ -1191,9 +1191,8 @@ void MainFrame::init_menubar_as_editor()
[this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue", nullptr, []() {return true; }, this);
windowMenu->AppendSeparator();
append_menu_item(windowMenu, wxID_ANY, _L("Open new instance") + "\tCtrl+I", _L("Open a new PrusaSlicer instance"),
append_menu_item(windowMenu, wxID_ANY, _L("Open new instance") + "\tCtrl+Shift+I", _L("Open a new PrusaSlicer instance"),
[this](wxCommandEvent&) { start_new_slicer(); }, "", nullptr, [this]() {return m_plater != nullptr && wxGetApp().app_config->get("single_instance") != "1"; }, this);
}
// View menu

View file

@ -178,7 +178,7 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/)
#ifdef __WXMSW__
// when we use 2 monitors with different DPIs, GetTextExtent() return value for the primary display
// so, use dc.GetMultiLineTextExtent on Windows
wxPaintDC dc(this);
wxClientDC dc(this);
dc.SetFont(m_font);
dc.GetMultiLineTextExtent(label, &label_w, &label_h);
#else

View file

@ -25,20 +25,22 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id) {
const t_field& OptionsGroup::build_field(const t_config_option_key& id, const ConfigOptionDef& opt) {
// Check the gui_type field first, fall through
// is the normal type.
if (opt.gui_type.compare("select") == 0) {
} else if (opt.gui_type.compare("select_open") == 0) {
if (opt.gui_type == "select") {
} else if (opt.gui_type == "select_open") {
m_fields.emplace(id, std::move(Choice::Create<Choice>(this->ctrl_parent(), opt, id)));
} else if (opt.gui_type.compare("color") == 0) {
} else if (opt.gui_type == "color") {
m_fields.emplace(id, std::move(ColourPicker::Create<ColourPicker>(this->ctrl_parent(), opt, id)));
} else if (opt.gui_type.compare("f_enum_open") == 0 ||
opt.gui_type.compare("i_enum_open") == 0 ||
opt.gui_type.compare("i_enum_closed") == 0) {
} else if (opt.gui_type == "f_enum_open" ||
opt.gui_type == "i_enum_open" ||
opt.gui_type == "i_enum_closed") {
m_fields.emplace(id, std::move(Choice::Create<Choice>(this->ctrl_parent(), opt, id)));
} else if (opt.gui_type.compare("slider") == 0) {
} else if (opt.gui_type == "slider") {
m_fields.emplace(id, std::move(SliderCtrl::Create<SliderCtrl>(this->ctrl_parent(), opt, id)));
} else if (opt.gui_type.compare("i_spin") == 0) { // Spinctrl
} else if (opt.gui_type.compare("legend") == 0) { // StaticText
} else if (opt.gui_type == "i_spin") { // Spinctrl
} else if (opt.gui_type == "legend") { // StaticText
m_fields.emplace(id, std::move(StaticText::Create<StaticText>(this->ctrl_parent(), opt, id)));
} else if (opt.gui_type == "one_string") {
m_fields.emplace(id, std::move(TextCtrl::Create<TextCtrl>(this->ctrl_parent(), opt, id)));
} else {
switch (opt.type) {
case coFloatOrPercent:
@ -837,9 +839,9 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
}
if (config.option<ConfigOptionStrings>(opt_key)->values.empty())
ret = text_value;
else if (opt->gui_flags.compare("serialized") == 0) {
else if (opt->gui_flags == "serialized") {
std::vector<std::string> values = config.option<ConfigOptionStrings>(opt_key)->values;
if (!values.empty() && values[0].compare("") != 0)
if (!values.empty() && !values[0].empty())
for (auto el : values)
text_value += el + ";";
ret = text_value;
@ -897,6 +899,8 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
case coPoints:
if (opt_key == "bed_shape")
ret = config.option<ConfigOptionPoints>(opt_key)->values;
if (opt_key == "thumbnails")
ret = get_thumbnails_string(config.option<ConfigOptionPoints>(opt_key)->values);
else
ret = config.option<ConfigOptionPoints>(opt_key)->get_at(idx);
break;

View file

@ -2399,18 +2399,18 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
wxGetApp().sidebar().update_ui_from_settings();
};
if (imperial_units)
convert_from_imperial_units(model);
else if (model.looks_like_imperial_units()) {
wxMessageDialog msg_dlg(q, format_wxstr(_L(
"Some object(s) in file %s looks like saved in inches.\n"
"Should I consider them as a saved in inches and convert them?"), from_path(filename)) + "\n",
_L("The object appears to be saved in inches"), wxICON_WARNING | wxYES | wxNO);
if (msg_dlg.ShowModal() == wxID_YES)
if (!is_project_file) {
if (imperial_units)
convert_from_imperial_units(model);
}
else if (model.looks_like_imperial_units()) {
wxMessageDialog msg_dlg(q, format_wxstr(_L(
"Some object(s) in file %s looks like saved in inches.\n"
"Should I consider them as a saved in inches and convert them?"), from_path(filename)) + "\n",
_L("The object appears to be saved in inches"), wxICON_WARNING | wxYES | wxNO);
if (msg_dlg.ShowModal() == wxID_YES)
convert_from_imperial_units(model);
}
if (! is_project_file) {
if (model.looks_like_multipart_object()) {
wxMessageDialog msg_dlg(q, _L(
"This file contains several objects positioned at multiple heights.\n"

View file

@ -94,7 +94,7 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty
int cnt = 0;
if ( (type == Preset::TYPE_SLA_MATERIAL || type == Preset::TYPE_PRINTER) && opt_key != "bed_shape")
if ( (type == Preset::TYPE_SLA_MATERIAL || type == Preset::TYPE_PRINTER) && opt_key != "bed_shape" && opt_key != "thumbnails")
switch (config->option(opt_key)->type())
{
case coInts: change_opt_key<ConfigOptionInts >(opt_key, config, cnt); break;

View file

@ -109,14 +109,14 @@ Tab::Tab(wxNotebook* parent, const wxString& title, Preset::Type type) :
m_compatible_printers.type = Preset::TYPE_PRINTER;
m_compatible_printers.key_list = "compatible_printers";
m_compatible_printers.key_condition = "compatible_printers_condition";
m_compatible_printers.dialog_title = _(L("Compatible printers")).ToUTF8();
m_compatible_printers.dialog_label = _(L("Select the printers this profile is compatible with.")).ToUTF8();
m_compatible_printers.dialog_title = _L("Compatible printers");
m_compatible_printers.dialog_label = _L("Select the printers this profile is compatible with.");
m_compatible_prints.type = Preset::TYPE_PRINT;
m_compatible_prints.key_list = "compatible_prints";
m_compatible_prints.key_condition = "compatible_prints_condition";
m_compatible_prints.dialog_title = _(L("Compatible print profiles")).ToUTF8();
m_compatible_prints.dialog_label = _(L("Select the print profiles this profile is compatible with.")).ToUTF8();
m_compatible_prints.dialog_title = _L("Compatible print profiles");
m_compatible_prints.dialog_label = _L("Select the print profiles this profile is compatible with.");
wxGetApp().tabs_list.push_back(this);
@ -542,7 +542,7 @@ void Tab::decorate()
wxColour* colored_label_clr = nullptr;
if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" ||
opt.first == "compatible_prints" || opt.first == "compatible_printers")
opt.first == "compatible_prints" || opt.first == "compatible_printers")
colored_label_clr = (m_colored_Label_colors.find(opt.first) == m_colored_Label_colors.end()) ? nullptr : m_colored_Label_colors.at(opt.first);
if (!colored_label_clr) {
@ -653,7 +653,7 @@ void TabPrinter::init_options_list()
for (const auto opt_key : m_config->keys())
{
if (opt_key == "bed_shape") {
if (opt_key == "bed_shape" || opt_key == "thumbnails") {
m_options_list.emplace(opt_key, m_opt_status_value);
continue;
}
@ -1424,6 +1424,7 @@ void TabPrint::build()
optgroup->append_single_option_line("extra_perimeters", category_path + "extra-perimeters-if-needed");
optgroup->append_single_option_line("ensure_vertical_shell_thickness", category_path + "ensure-vertical-shell-thickness");
optgroup->append_single_option_line("avoid_crossing_perimeters", category_path + "avoid-crossing-perimeters");
optgroup->append_single_option_line("avoid_crossing_perimeters_max_detour", category_path + "avoid_crossing_perimeters_max_detour");
optgroup->append_single_option_line("thin_walls", category_path + "detect-thin-walls");
optgroup->append_single_option_line("overhangs", category_path + "detect-bridging-perimeters");
@ -2173,6 +2174,11 @@ void TabPrinter::build_fff()
optgroup = page->new_optgroup(L("Firmware"));
optgroup->append_single_option_line("gcode_flavor");
option = optgroup->get_option("thumbnails");
option.opt.full_width = true;
optgroup->append_single_option_line(option);
optgroup->append_single_option_line("silent_mode");
optgroup->append_single_option_line("remaining_times");

View file

@ -136,8 +136,8 @@ protected:
ScalableButton *btn = nullptr;
std::string key_list; // "compatible_printers"
std::string key_condition;
std::string dialog_title;
std::string dialog_label;
wxString dialog_title;
wxString dialog_label;
};
PresetDependencies m_compatible_printers;
PresetDependencies m_compatible_prints;

View file

@ -968,6 +968,9 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig&
BedShape shape(*config.option<ConfigOptionPoints>(opt_key));
return shape.get_full_name_with_params();
}
if (opt_key == "thumbnails")
return get_thumbnails_string(config.option<ConfigOptionPoints>(opt_key)->values);
Vec2d val = config.opt<ConfigOptionPoints>(opt_key)->get_at(opt_idx);
return from_u8((boost::format("[%1%]") % ConfigOptionPoint(val).serialize()).str());
}

View file

@ -62,7 +62,6 @@ set(XS_XSP_FILES
${XSP_DIR}/Layer.xsp
${XSP_DIR}/Line.xsp
${XSP_DIR}/Model.xsp
${XSP_DIR}/MotionPlanner.xsp
${XSP_DIR}/PerimeterGenerator.xsp
${XSP_DIR}/PlaceholderParser.xsp
${XSP_DIR}/Point.xsp

View file

@ -32,7 +32,6 @@ REGISTER_CLASS(ModelMaterial, "Model::Material");
REGISTER_CLASS(ModelObject, "Model::Object");
REGISTER_CLASS(ModelVolume, "Model::Volume");
REGISTER_CLASS(ModelInstance, "Model::Instance");
REGISTER_CLASS(MotionPlanner, "MotionPlanner");
REGISTER_CLASS(BoundingBox, "Geometry::BoundingBox");
REGISTER_CLASS(BoundingBoxf, "Geometry::BoundingBoxf");
REGISTER_CLASS(BoundingBoxf3, "Geometry::BoundingBoxf3");

View file

@ -1,92 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../../lib";
}
use Slic3r::XS;
use Test::More tests => 20;
my $square = Slic3r::Polygon->new( # ccw
[100, 100],
[200, 100],
[200, 200],
[100, 200],
);
my $hole_in_square = Slic3r::Polygon->new( # cw
[140, 140],
[140, 160],
[160, 160],
[160, 140],
);
$_->scale(1/0.000001) for $square, $hole_in_square;
my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square);
{
my $mp = Slic3r::MotionPlanner->new([ $expolygon ]);
isa_ok $mp, 'Slic3r::MotionPlanner';
my $from = Slic3r::Point->new(120, 120);
my $to = Slic3r::Point->new(180,180);
$_->scale(1/0.000001) for $from, $to;
my $path = $mp->shortest_path($from, $to);
ok $path->is_valid(), 'return path is valid';
ok $path->length > Slic3r::Line->new($from, $to)->length, 'path length is greater than straight line';
ok $path->first_point->coincides_with($from), 'first path point coincides with initial point';
ok $path->last_point->coincides_with($to), 'last path point coincides with destination point';
ok $expolygon->contains_polyline($path), 'path is fully contained in expolygon';
}
{
my $mp = Slic3r::MotionPlanner->new([ $expolygon ]);
isa_ok $mp, 'Slic3r::MotionPlanner';
my $from = Slic3r::Point->new(80, 100);
my $to = Slic3r::Point->new(220,200);
$_->scale(1/0.000001) for $from, $to;
my $path = $mp->shortest_path($from, $to);
ok $path->is_valid(), 'return path is valid';
ok $path->length > Slic3r::Line->new($from, $to)->length, 'path length is greater than straight line';
ok $path->first_point->coincides_with($from), 'first path point coincides with initial point';
ok $path->last_point->coincides_with($to), 'last path point coincides with destination point';
is scalar(@{ Slic3r::Geometry::Clipper::intersection_pl([$path], [@$expolygon]) }), 0, 'path has no intersection with expolygon';
}
{
my $expolygon2 = $expolygon->clone;
$expolygon2->translate(300/0.000001, 0);
my $mp = Slic3r::MotionPlanner->new([ $expolygon, $expolygon2 ]);
isa_ok $mp, 'Slic3r::MotionPlanner';
my $from = Slic3r::Point->new(120, 120);
my $to = Slic3r::Point->new(120 + 300, 120);
$_->scale(1/0.000001) for $from, $to;
ok $expolygon->contains_point($from), 'start point is contained in first expolygon';
ok $expolygon2->contains_point($to), 'end point is contained in second expolygon';
my $path = $mp->shortest_path($from, $to);
ok $path->is_valid(), 'return path is valid';
}
{
my $expolygons = [
Slic3r::ExPolygon->new([[123800962,89330311],[123959159,89699438],[124000004,89898430],[124000012,110116427],[123946510,110343065],[123767391,110701303],[123284087,111000001],[102585791,111000009],[102000004,110414223],[102000004,89585787],[102585790,89000000],[123300022,88999993]]),
Slic3r::ExPolygon->new([[97800954,89330311],[97959151,89699438],[97999996,89898430],[98000004,110116427],[97946502,110343065],[97767383,110701303],[97284079,111000001],[76585783,111000009],[75999996,110414223],[75999996,89585787],[76585782,89000000],[97300014,88999993]]),
];
my $mp = Slic3r::MotionPlanner->new($expolygons);
isa_ok $mp, 'Slic3r::MotionPlanner';
my $from = Slic3r::Point->new(79120520, 107839491);
my $to = Slic3r::Point->new(104664164, 108335852);
ok $expolygons->[1]->contains_point($from), 'start point is contained in second expolygon';
ok $expolygons->[0]->contains_point($to), 'end point is contained in first expolygon';
my $path = $mp->shortest_path($from, $to);
ok $path->is_valid(), 'return path is valid';
}
__END__

View file

@ -1,15 +0,0 @@
%module{Slic3r::XS};
%{
#include <xsinit.h>
#include "libslic3r/MotionPlanner.hpp"
%}
%name{Slic3r::MotionPlanner} class MotionPlanner {
MotionPlanner(ExPolygons islands);
~MotionPlanner();
int islands_count();
Clone<Polyline> shortest_path(Point* from, Point* to)
%code%{ RETVAL = THIS->shortest_path(*from, *to); %};
};

View file

@ -191,18 +191,6 @@ GCode* O_OBJECT_SLIC3R
Ref<GCode> O_OBJECT_SLIC3R_T
Clone<GCode> O_OBJECT_SLIC3R_T
//GCodePreviewData* O_OBJECT_SLIC3R
//Ref<GCodePreviewData> O_OBJECT_SLIC3R_T
//Clone<GCodePreviewData> O_OBJECT_SLIC3R_T
MotionPlanner* O_OBJECT_SLIC3R
Ref<MotionPlanner> O_OBJECT_SLIC3R_T
Clone<MotionPlanner> O_OBJECT_SLIC3R_T
// GCodeSender* O_OBJECT_SLIC3R
// Ref<GCodeSender> O_OBJECT_SLIC3R_T
// Clone<GCodeSender> O_OBJECT_SLIC3R_T
BridgeDetector* O_OBJECT_SLIC3R
Ref<BridgeDetector> O_OBJECT_SLIC3R_T
Clone<BridgeDetector> O_OBJECT_SLIC3R_T

View file

@ -97,12 +97,6 @@
%typemap{PolylineCollection*};
%typemap{Ref<PolylineCollection>}{simple};
%typemap{Clone<PolylineCollection>}{simple};
%typemap{MotionPlanner*};
%typemap{Ref<MotionPlanner>}{simple};
%typemap{Clone<MotionPlanner>}{simple};
// %typemap{GCodeSender*};
// %typemap{Ref<GCodeSender>}{simple};
// %typemap{Clone<GCodeSender>}{simple};
%typemap{BridgeDetector*};
%typemap{Ref<BridgeDetector>}{simple};
%typemap{Clone<BridgeDetector>}{simple};