mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-12 09:17:52 -06:00
Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_3dconnexion
This commit is contained in:
commit
644cc8c6b4
23 changed files with 161 additions and 232 deletions
|
@ -13,12 +13,12 @@ typedef std::vector<ExPolygonCollection> ExPolygonCollections;
|
||||||
|
|
||||||
class ExPolygonCollection
|
class ExPolygonCollection
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ExPolygons expolygons;
|
ExPolygons expolygons;
|
||||||
|
|
||||||
ExPolygonCollection() {};
|
ExPolygonCollection() {}
|
||||||
ExPolygonCollection(const ExPolygon &expolygon);
|
ExPolygonCollection(const ExPolygon &expolygon);
|
||||||
ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {};
|
ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {}
|
||||||
operator Points() const;
|
operator Points() const;
|
||||||
operator Polygons() const;
|
operator Polygons() const;
|
||||||
operator ExPolygons&();
|
operator ExPolygons&();
|
||||||
|
|
|
@ -14,7 +14,7 @@ class ExtrusionEntityCollection;
|
||||||
class Extruder;
|
class Extruder;
|
||||||
|
|
||||||
// Each ExtrusionRole value identifies a distinct set of { extruder, speed }
|
// Each ExtrusionRole value identifies a distinct set of { extruder, speed }
|
||||||
enum ExtrusionRole {
|
enum ExtrusionRole : uint8_t {
|
||||||
erNone,
|
erNone,
|
||||||
erPerimeter,
|
erPerimeter,
|
||||||
erExternalPerimeter,
|
erExternalPerimeter,
|
||||||
|
@ -117,25 +117,16 @@ public:
|
||||||
float width;
|
float width;
|
||||||
// Height of the extrusion, used for visualization purposes.
|
// Height of the extrusion, used for visualization purposes.
|
||||||
float height;
|
float height;
|
||||||
// Feedrate of the extrusion, used for visualization purposes.
|
|
||||||
float feedrate;
|
|
||||||
// Id of the extruder, used for visualization purposes.
|
|
||||||
unsigned int extruder_id;
|
|
||||||
// Id of the color, used for visualization purposes in the color printing case.
|
|
||||||
unsigned int cp_color_id;
|
|
||||||
// Fan speed for the extrusion, used for visualization purposes.
|
|
||||||
float fan_speed;
|
|
||||||
|
|
||||||
ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), feedrate(0.0f), extruder_id(0), cp_color_id(0), fan_speed(0.0f), m_role(role) {};
|
ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {};
|
||||||
ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), feedrate(0.0f), extruder_id(0), cp_color_id(0), fan_speed(0.0f), m_role(role) {};
|
ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {};
|
||||||
ExtrusionPath(const ExtrusionPath& rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), fan_speed(rhs.fan_speed), m_role(rhs.m_role) {}
|
ExtrusionPath(const ExtrusionPath& rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
|
||||||
ExtrusionPath(ExtrusionPath&& rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), fan_speed(rhs.fan_speed), m_role(rhs.m_role) {}
|
ExtrusionPath(ExtrusionPath&& rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
|
||||||
ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), fan_speed(rhs.fan_speed), m_role(rhs.m_role) {}
|
ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
|
||||||
ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), fan_speed(rhs.fan_speed), m_role(rhs.m_role) {}
|
ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
|
||||||
// ExtrusionPath(ExtrusionRole role, const Flow &flow) : m_role(role), mm3_per_mm(flow.mm3_per_mm()), width(flow.width), height(flow.height), feedrate(0.0f), extruder_id(0) {};
|
|
||||||
|
|
||||||
ExtrusionPath& operator=(const ExtrusionPath& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->cp_color_id = rhs.cp_color_id, this->fan_speed = rhs.fan_speed, this->polyline = rhs.polyline; return *this; }
|
ExtrusionPath& operator=(const ExtrusionPath& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = rhs.polyline; return *this; }
|
||||||
ExtrusionPath& operator=(ExtrusionPath&& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->cp_color_id = rhs.cp_color_id, this->fan_speed = rhs.fan_speed, this->polyline = std::move(rhs.polyline); return *this; }
|
ExtrusionPath& operator=(ExtrusionPath&& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = std::move(rhs.polyline); return *this; }
|
||||||
|
|
||||||
ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); }
|
ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); }
|
||||||
// Create a new object, initialize it with this object using the move semantics.
|
// Create a new object, initialize it with this object using the move semantics.
|
||||||
|
|
|
@ -117,11 +117,11 @@ Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectP
|
||||||
const Layer* layer1 = object->layers()[i * 2];
|
const Layer* layer1 = object->layers()[i * 2];
|
||||||
const Layer* layer2 = object->layers()[i * 2 + 1];
|
const Layer* layer2 = object->layers()[i * 2 + 1];
|
||||||
Polygons polys;
|
Polygons polys;
|
||||||
polys.reserve(layer1->slices.expolygons.size() + layer2->slices.expolygons.size());
|
polys.reserve(layer1->slices.size() + layer2->slices.size());
|
||||||
for (const ExPolygon &expoly : layer1->slices.expolygons)
|
for (const ExPolygon &expoly : layer1->slices)
|
||||||
//FIXME no holes?
|
//FIXME no holes?
|
||||||
polys.emplace_back(expoly.contour);
|
polys.emplace_back(expoly.contour);
|
||||||
for (const ExPolygon &expoly : layer2->slices.expolygons)
|
for (const ExPolygon &expoly : layer2->slices)
|
||||||
//FIXME no holes?
|
//FIXME no holes?
|
||||||
polys.emplace_back(expoly.contour);
|
polys.emplace_back(expoly.contour);
|
||||||
polygons_per_layer[i] = union_(polys);
|
polygons_per_layer[i] = union_(polys);
|
||||||
|
@ -130,8 +130,8 @@ Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectP
|
||||||
if (object->layers().size() & 1) {
|
if (object->layers().size() & 1) {
|
||||||
const Layer *layer = object->layers().back();
|
const Layer *layer = object->layers().back();
|
||||||
Polygons polys;
|
Polygons polys;
|
||||||
polys.reserve(layer->slices.expolygons.size());
|
polys.reserve(layer->slices.size());
|
||||||
for (const ExPolygon &expoly : layer->slices.expolygons)
|
for (const ExPolygon &expoly : layer->slices)
|
||||||
//FIXME no holes?
|
//FIXME no holes?
|
||||||
polys.emplace_back(expoly.contour);
|
polys.emplace_back(expoly.contour);
|
||||||
polygons_per_layer.back() = union_(polys);
|
polygons_per_layer.back() = union_(polys);
|
||||||
|
@ -1802,11 +1802,8 @@ void GCode::process_layer(
|
||||||
// - for each island, we extrude perimeters first, unless user set the infill_first
|
// - for each island, we extrude perimeters first, unless user set the infill_first
|
||||||
// option
|
// option
|
||||||
// (Still, we have to keep track of regions because we need to apply their config)
|
// (Still, we have to keep track of regions because we need to apply their config)
|
||||||
size_t n_slices = layer.slices.expolygons.size();
|
size_t n_slices = layer.slices.size();
|
||||||
std::vector<BoundingBox> layer_surface_bboxes;
|
const std::vector<BoundingBox> &layer_surface_bboxes = layer.slices_bboxes;
|
||||||
layer_surface_bboxes.reserve(n_slices);
|
|
||||||
for (const ExPolygon &expoly : layer.slices.expolygons)
|
|
||||||
layer_surface_bboxes.push_back(get_extents(expoly.contour));
|
|
||||||
// Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first,
|
// Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first,
|
||||||
// so we can just test a point inside ExPolygon::contour and we may skip testing the holes.
|
// so we can just test a point inside ExPolygon::contour and we may skip testing the holes.
|
||||||
std::vector<size_t> slices_test_order;
|
std::vector<size_t> slices_test_order;
|
||||||
|
@ -1822,7 +1819,7 @@ void GCode::process_layer(
|
||||||
const BoundingBox &bbox = layer_surface_bboxes[i];
|
const BoundingBox &bbox = layer_surface_bboxes[i];
|
||||||
return point(0) >= bbox.min(0) && point(0) < bbox.max(0) &&
|
return point(0) >= bbox.min(0) && point(0) < bbox.max(0) &&
|
||||||
point(1) >= bbox.min(1) && point(1) < bbox.max(1) &&
|
point(1) >= bbox.min(1) && point(1) < bbox.max(1) &&
|
||||||
layer.slices.expolygons[i].contour.contains(point);
|
layer.slices[i].contour.contains(point);
|
||||||
};
|
};
|
||||||
|
|
||||||
for (size_t region_id = 0; region_id < print.regions().size(); ++ region_id) {
|
for (size_t region_id = 0; region_id < print.regions().size(); ++ region_id) {
|
||||||
|
@ -2418,7 +2415,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
|
||||||
static int iRun = 0;
|
static int iRun = 0;
|
||||||
SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++));
|
SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++));
|
||||||
if (m_layer->lower_layer != NULL)
|
if (m_layer->lower_layer != NULL)
|
||||||
svg.draw(m_layer->lower_layer->slices.expolygons);
|
svg.draw(m_layer->lower_layer->slices);
|
||||||
for (size_t i = 0; i < loop.paths.size(); ++ i)
|
for (size_t i = 0; i < loop.paths.size(); ++ i)
|
||||||
svg.draw(loop.paths[i].as_polyline(), "red");
|
svg.draw(loop.paths[i].as_polyline(), "red");
|
||||||
Polylines polylines;
|
Polylines polylines;
|
||||||
|
|
|
@ -866,7 +866,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
|
||||||
}
|
}
|
||||||
|
|
||||||
// if layer not found, create and return it
|
// if layer not found, create and return it
|
||||||
layers.emplace_back(z, ExtrusionPaths());
|
layers.emplace_back(z, GCodePreviewData::Extrusion::Paths());
|
||||||
return layers.back();
|
return layers.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -875,14 +875,18 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
|
||||||
// if the polyline is valid, create the extrusion path from it and store it
|
// if the polyline is valid, create the extrusion path from it and store it
|
||||||
if (polyline.is_valid())
|
if (polyline.is_valid())
|
||||||
{
|
{
|
||||||
ExtrusionPath path(data.extrusion_role, data.mm3_per_mm, data.width, data.height);
|
auto& paths = get_layer_at_z(preview_data.extrusion.layers, z).paths;
|
||||||
|
paths.emplace_back(GCodePreviewData::Extrusion::Path());
|
||||||
|
GCodePreviewData::Extrusion::Path &path = paths.back();
|
||||||
path.polyline = polyline;
|
path.polyline = polyline;
|
||||||
|
path.extrusion_role = data.extrusion_role;
|
||||||
|
path.mm3_per_mm = data.mm3_per_mm;
|
||||||
|
path.width = data.width;
|
||||||
|
path.height = data.height;
|
||||||
path.feedrate = data.feedrate;
|
path.feedrate = data.feedrate;
|
||||||
path.extruder_id = data.extruder_id;
|
path.extruder_id = data.extruder_id;
|
||||||
path.fan_speed = data.fan_speed;
|
|
||||||
path.cp_color_id = data.cp_color_id;
|
path.cp_color_id = data.cp_color_id;
|
||||||
|
path.fan_speed = data.fan_speed;
|
||||||
get_layer_at_z(preview_data.extrusion.layers, z).paths.push_back(path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,7 +23,7 @@ std::vector<unsigned char> GCodePreviewData::Color::as_bytes() const
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
GCodePreviewData::Extrusion::Layer::Layer(float z, const ExtrusionPaths& paths)
|
GCodePreviewData::Extrusion::Layer::Layer(float z, const Paths& paths)
|
||||||
: z(z)
|
: z(z)
|
||||||
, paths(paths)
|
, paths(paths)
|
||||||
{
|
{
|
||||||
|
@ -171,8 +171,8 @@ size_t GCodePreviewData::Extrusion::memory_used() const
|
||||||
size_t out = sizeof(*this);
|
size_t out = sizeof(*this);
|
||||||
out += SLIC3R_STDVEC_MEMSIZE(this->layers, Layer);
|
out += SLIC3R_STDVEC_MEMSIZE(this->layers, Layer);
|
||||||
for (const Layer &layer : this->layers) {
|
for (const Layer &layer : this->layers) {
|
||||||
out += SLIC3R_STDVEC_MEMSIZE(layer.paths, ExtrusionPath);
|
out += SLIC3R_STDVEC_MEMSIZE(layer.paths, Path);
|
||||||
for (const ExtrusionPath &path : layer.paths)
|
for (const Path &path : layer.paths)
|
||||||
out += SLIC3R_STDVEC_MEMSIZE(path.polyline.points, Point);
|
out += SLIC3R_STDVEC_MEMSIZE(path.polyline.points, Point);
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
|
|
@ -87,12 +87,34 @@ public:
|
||||||
static const std::string Default_Extrusion_Role_Names[erCount];
|
static const std::string Default_Extrusion_Role_Names[erCount];
|
||||||
static const EViewType Default_View_Type;
|
static const EViewType Default_View_Type;
|
||||||
|
|
||||||
|
class Path
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Polyline polyline;
|
||||||
|
ExtrusionRole extrusion_role;
|
||||||
|
// Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator.
|
||||||
|
float mm3_per_mm;
|
||||||
|
// Width of the extrusion, used for visualization purposes.
|
||||||
|
float width;
|
||||||
|
// Height of the extrusion, used for visualization purposes.
|
||||||
|
float height;
|
||||||
|
// Feedrate of the extrusion, used for visualization purposes.
|
||||||
|
float feedrate;
|
||||||
|
// Id of the extruder, used for visualization purposes.
|
||||||
|
uint32_t extruder_id;
|
||||||
|
// Id of the color, used for visualization purposes in the color printing case.
|
||||||
|
uint32_t cp_color_id;
|
||||||
|
// Fan speed for the extrusion, used for visualization purposes.
|
||||||
|
float fan_speed;
|
||||||
|
};
|
||||||
|
using Paths = std::vector<Path>;
|
||||||
|
|
||||||
struct Layer
|
struct Layer
|
||||||
{
|
{
|
||||||
float z;
|
float z;
|
||||||
ExtrusionPaths paths;
|
Paths paths;
|
||||||
|
|
||||||
Layer(float z, const ExtrusionPaths& paths);
|
Layer(float z, const Paths& paths);
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::vector<Layer> LayersList;
|
typedef std::vector<Layer> LayersList;
|
||||||
|
|
|
@ -47,8 +47,8 @@ void Layer::make_slices()
|
||||||
slices = union_ex(slices_p);
|
slices = union_ex(slices_p);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->slices.expolygons.clear();
|
this->slices.clear();
|
||||||
this->slices.expolygons.reserve(slices.size());
|
this->slices.reserve(slices.size());
|
||||||
|
|
||||||
// prepare ordering points
|
// prepare ordering points
|
||||||
Points ordering_points;
|
Points ordering_points;
|
||||||
|
@ -61,7 +61,7 @@ void Layer::make_slices()
|
||||||
|
|
||||||
// populate slices vector
|
// populate slices vector
|
||||||
for (size_t i : order)
|
for (size_t i : order)
|
||||||
this->slices.expolygons.push_back(std::move(slices[i]));
|
this->slices.push_back(std::move(slices[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge typed slices into untyped slices. This method is used to revert the effects of detect_surfaces_type() called for posPrepareInfill.
|
// Merge typed slices into untyped slices. This method is used to revert the effects of detect_surfaces_type() called for posPrepareInfill.
|
||||||
|
@ -70,7 +70,7 @@ void Layer::merge_slices()
|
||||||
if (m_regions.size() == 1) {
|
if (m_regions.size() == 1) {
|
||||||
// Optimization, also more robust. Don't merge classified pieces of layerm->slices,
|
// Optimization, also more robust. Don't merge classified pieces of layerm->slices,
|
||||||
// but use the non-split islands of a layer. For a single region print, these shall be equal.
|
// but use the non-split islands of a layer. For a single region print, these shall be equal.
|
||||||
m_regions.front()->slices.set(this->slices.expolygons, stInternal);
|
m_regions.front()->slices.set(this->slices, stInternal);
|
||||||
} else {
|
} else {
|
||||||
for (LayerRegion *layerm : m_regions)
|
for (LayerRegion *layerm : m_regions)
|
||||||
// without safety offset, artifacts are generated (GH #2494)
|
// without safety offset, artifacts are generated (GH #2494)
|
||||||
|
|
|
@ -110,7 +110,8 @@ public:
|
||||||
// also known as 'islands' (all regions and surface types are merged here)
|
// also known as 'islands' (all regions and surface types are merged here)
|
||||||
// The slices are chained by the shortest traverse distance and this traversal
|
// The slices are chained by the shortest traverse distance and this traversal
|
||||||
// order will be recovered by the G-code generator.
|
// order will be recovered by the G-code generator.
|
||||||
ExPolygonCollection slices;
|
ExPolygons slices;
|
||||||
|
std::vector<BoundingBox> slices_bboxes;
|
||||||
|
|
||||||
size_t region_count() const { return m_regions.size(); }
|
size_t region_count() const { return m_regions.size(); }
|
||||||
const LayerRegion* get_region(int idx) const { return m_regions.at(idx); }
|
const LayerRegion* get_region(int idx) const { return m_regions.at(idx); }
|
||||||
|
|
|
@ -140,7 +140,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||||
// Remove voids from fill_boundaries, that are not supported by the layer below.
|
// Remove voids from fill_boundaries, that are not supported by the layer below.
|
||||||
if (lower_layer_covered == nullptr) {
|
if (lower_layer_covered == nullptr) {
|
||||||
lower_layer_covered = &lower_layer_covered_tmp;
|
lower_layer_covered = &lower_layer_covered_tmp;
|
||||||
lower_layer_covered_tmp = to_polygons(lower_layer->slices.expolygons);
|
lower_layer_covered_tmp = to_polygons(lower_layer->slices);
|
||||||
}
|
}
|
||||||
if (! lower_layer_covered->empty())
|
if (! lower_layer_covered->empty())
|
||||||
voids = diff(voids, *lower_layer_covered);
|
voids = diff(voids, *lower_layer_covered);
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "ExPolygonCollection.hpp"
|
|
||||||
#include "Flow.hpp"
|
#include "Flow.hpp"
|
||||||
#include "Polygon.hpp"
|
#include "Polygon.hpp"
|
||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
|
@ -15,7 +14,7 @@ class PerimeterGenerator {
|
||||||
public:
|
public:
|
||||||
// Inputs:
|
// Inputs:
|
||||||
const SurfaceCollection *slices;
|
const SurfaceCollection *slices;
|
||||||
const ExPolygonCollection *lower_slices;
|
const ExPolygons *lower_slices;
|
||||||
double layer_height;
|
double layer_height;
|
||||||
int layer_id;
|
int layer_id;
|
||||||
Flow perimeter_flow;
|
Flow perimeter_flow;
|
||||||
|
@ -45,7 +44,7 @@ public:
|
||||||
ExtrusionEntityCollection* gap_fill,
|
ExtrusionEntityCollection* gap_fill,
|
||||||
// Infills without the gap fills
|
// Infills without the gap fills
|
||||||
SurfaceCollection* fill_surfaces)
|
SurfaceCollection* fill_surfaces)
|
||||||
: slices(slices), lower_slices(NULL), layer_height(layer_height),
|
: slices(slices), lower_slices(nullptr), layer_height(layer_height),
|
||||||
layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow),
|
layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow),
|
||||||
overhang_flow(flow), solid_infill_flow(flow),
|
overhang_flow(flow), solid_infill_flow(flow),
|
||||||
config(config), object_config(object_config), print_config(print_config),
|
config(config), object_config(object_config), print_config(print_config),
|
||||||
|
|
|
@ -328,17 +328,6 @@ unsigned int Print::num_object_instances() const
|
||||||
return instances;
|
return instances;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Print::_simplify_slices(double distance)
|
|
||||||
{
|
|
||||||
for (PrintObject *object : m_objects) {
|
|
||||||
for (Layer *layer : object->m_layers) {
|
|
||||||
layer->slices.simplify(distance);
|
|
||||||
for (LayerRegion *layerm : layer->regions())
|
|
||||||
layerm->slices.simplify(distance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double Print::max_allowed_layer_height() const
|
double Print::max_allowed_layer_height() const
|
||||||
{
|
{
|
||||||
double nozzle_diameter_max = 0.;
|
double nozzle_diameter_max = 0.;
|
||||||
|
@ -1114,6 +1103,9 @@ std::string Print::validate() const
|
||||||
if (m_objects.empty())
|
if (m_objects.empty())
|
||||||
return L("All objects are outside of the print volume.");
|
return L("All objects are outside of the print volume.");
|
||||||
|
|
||||||
|
if (extruders().empty())
|
||||||
|
return L("The supplied settings will cause an empty print.");
|
||||||
|
|
||||||
if (m_config.complete_objects) {
|
if (m_config.complete_objects) {
|
||||||
// Check horizontal clearance.
|
// Check horizontal clearance.
|
||||||
{
|
{
|
||||||
|
@ -1271,10 +1263,7 @@ std::string Print::validate() const
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// find the smallest nozzle diameter
|
|
||||||
std::vector<unsigned int> extruders = this->extruders();
|
std::vector<unsigned int> extruders = this->extruders();
|
||||||
if (extruders.empty())
|
|
||||||
return L("The supplied settings will cause an empty print.");
|
|
||||||
|
|
||||||
// Find the smallest used nozzle diameter and the number of unique nozzle diameters.
|
// Find the smallest used nozzle diameter and the number of unique nozzle diameters.
|
||||||
double min_nozzle_diameter = std::numeric_limits<double>::max();
|
double min_nozzle_diameter = std::numeric_limits<double>::max();
|
||||||
|
@ -1593,7 +1582,7 @@ void Print::_make_skirt()
|
||||||
for (const Layer *layer : object->m_layers) {
|
for (const Layer *layer : object->m_layers) {
|
||||||
if (layer->print_z > skirt_height_z)
|
if (layer->print_z > skirt_height_z)
|
||||||
break;
|
break;
|
||||||
for (const ExPolygon &expoly : layer->slices.expolygons)
|
for (const ExPolygon &expoly : layer->slices)
|
||||||
// Collect the outer contour points only, ignore holes for the calculation of the convex hull.
|
// Collect the outer contour points only, ignore holes for the calculation of the convex hull.
|
||||||
append(object_points, expoly.contour.points);
|
append(object_points, expoly.contour.points);
|
||||||
}
|
}
|
||||||
|
@ -1704,7 +1693,7 @@ void Print::_make_brim()
|
||||||
Polygons islands;
|
Polygons islands;
|
||||||
for (PrintObject *object : m_objects) {
|
for (PrintObject *object : m_objects) {
|
||||||
Polygons object_islands;
|
Polygons object_islands;
|
||||||
for (ExPolygon &expoly : object->m_layers.front()->slices.expolygons)
|
for (ExPolygon &expoly : object->m_layers.front()->slices)
|
||||||
object_islands.push_back(expoly.contour);
|
object_islands.push_back(expoly.contour);
|
||||||
if (! object->support_layers().empty())
|
if (! object->support_layers().empty())
|
||||||
object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON));
|
object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON));
|
||||||
|
|
|
@ -180,7 +180,6 @@ private:
|
||||||
void _slice(const std::vector<coordf_t> &layer_height_profile);
|
void _slice(const std::vector<coordf_t> &layer_height_profile);
|
||||||
std::string _fix_slicing_errors();
|
std::string _fix_slicing_errors();
|
||||||
void _simplify_slices(double distance);
|
void _simplify_slices(double distance);
|
||||||
void _make_perimeters();
|
|
||||||
bool has_support_material() const;
|
bool has_support_material() const;
|
||||||
void detect_surfaces_type();
|
void detect_surfaces_type();
|
||||||
void process_external_surfaces();
|
void process_external_surfaces();
|
||||||
|
@ -383,7 +382,6 @@ private:
|
||||||
void _make_skirt();
|
void _make_skirt();
|
||||||
void _make_brim();
|
void _make_brim();
|
||||||
void _make_wipe_tower();
|
void _make_wipe_tower();
|
||||||
void _simplify_slices(double distance);
|
|
||||||
|
|
||||||
// Declared here to have access to Model / ModelObject / ModelInstance
|
// Declared here to have access to Model / ModelObject / ModelInstance
|
||||||
static void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_src);
|
static void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_src);
|
||||||
|
|
|
@ -2392,6 +2392,7 @@ void PrintConfigDef::init_sla_params()
|
||||||
"the threshold in the middle. This behaviour eliminates "
|
"the threshold in the middle. This behaviour eliminates "
|
||||||
"antialiasing without losing holes in polygons.");
|
"antialiasing without losing holes in polygons.");
|
||||||
def->min = 0;
|
def->min = 0;
|
||||||
|
def->max = 1;
|
||||||
def->mode = comExpert;
|
def->mode = comExpert;
|
||||||
def->set_default_value(new ConfigOptionFloat(1.0));
|
def->set_default_value(new ConfigOptionFloat(1.0));
|
||||||
|
|
||||||
|
|
|
@ -117,6 +117,19 @@ void PrintObject::slice()
|
||||||
// Simplify slices if required.
|
// Simplify slices if required.
|
||||||
if (m_print->config().resolution)
|
if (m_print->config().resolution)
|
||||||
this->_simplify_slices(scale_(this->print()->config().resolution));
|
this->_simplify_slices(scale_(this->print()->config().resolution));
|
||||||
|
// Update bounding boxes
|
||||||
|
tbb::parallel_for(
|
||||||
|
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||||
|
[this](const tbb::blocked_range<size_t>& range) {
|
||||||
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||||
|
m_print->throw_if_canceled();
|
||||||
|
Layer &layer = *m_layers[layer_idx];
|
||||||
|
layer.slices_bboxes.clear();
|
||||||
|
layer.slices_bboxes.reserve(layer.slices.size());
|
||||||
|
for (const ExPolygon &expoly : layer.slices)
|
||||||
|
layer.slices_bboxes.emplace_back(get_extents(expoly));
|
||||||
|
}
|
||||||
|
});
|
||||||
if (m_layers.empty())
|
if (m_layers.empty())
|
||||||
throw std::runtime_error("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n");
|
throw std::runtime_error("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n");
|
||||||
this->set_done(posSlice);
|
this->set_done(posSlice);
|
||||||
|
@ -865,7 +878,7 @@ void PrintObject::process_external_surfaces()
|
||||||
// Shrink the holes, let the layer above expand slightly inside the unsupported areas.
|
// Shrink the holes, let the layer above expand slightly inside the unsupported areas.
|
||||||
polygons_append(voids, offset(surface.expolygon, unsupported_width));
|
polygons_append(voids, offset(surface.expolygon, unsupported_width));
|
||||||
}
|
}
|
||||||
surfaces_covered[layer_idx] = diff(to_polygons(this->m_layers[layer_idx]->slices.expolygons), voids);
|
surfaces_covered[layer_idx] = diff(to_polygons(this->m_layers[layer_idx]->slices), voids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -975,8 +988,8 @@ void PrintObject::discover_vertical_shells()
|
||||||
polygons_append(cache.holes, offset(offset_ex(layer.slices, 0.3f * perimeter_min_spacing), - perimeter_offset - 0.3f * perimeter_min_spacing));
|
polygons_append(cache.holes, offset(offset_ex(layer.slices, 0.3f * perimeter_min_spacing), - perimeter_offset - 0.3f * perimeter_min_spacing));
|
||||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||||
{
|
{
|
||||||
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.slices.expolygons));
|
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.slices));
|
||||||
svg.draw(layer.slices.expolygons, "blue");
|
svg.draw(layer.slices, "blue");
|
||||||
svg.draw(union_ex(cache.holes), "red");
|
svg.draw(union_ex(cache.holes), "red");
|
||||||
svg.draw_outline(union_ex(cache.holes), "black", "blue", scale_(0.05));
|
svg.draw_outline(union_ex(cache.holes), "black", "blue", scale_(0.05));
|
||||||
svg.Close();
|
svg.Close();
|
||||||
|
@ -1659,7 +1672,8 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile)
|
||||||
// Trim volumes in a single layer, one by the other, possibly apply upscaling.
|
// Trim volumes in a single layer, one by the other, possibly apply upscaling.
|
||||||
{
|
{
|
||||||
Polygons processed;
|
Polygons processed;
|
||||||
for (SlicedVolume &sliced_volume : sliced_volumes) {
|
for (SlicedVolume &sliced_volume : sliced_volumes)
|
||||||
|
if (! sliced_volume.expolygons_by_layer.empty()) {
|
||||||
ExPolygons slices = std::move(sliced_volume.expolygons_by_layer[layer_id]);
|
ExPolygons slices = std::move(sliced_volume.expolygons_by_layer[layer_id]);
|
||||||
if (upscale)
|
if (upscale)
|
||||||
slices = offset_ex(std::move(slices), delta);
|
slices = offset_ex(std::move(slices), delta);
|
||||||
|
@ -1677,7 +1691,7 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile)
|
||||||
ExPolygons expolygons;
|
ExPolygons expolygons;
|
||||||
size_t num_volumes = 0;
|
size_t num_volumes = 0;
|
||||||
for (SlicedVolume &sliced_volume : sliced_volumes)
|
for (SlicedVolume &sliced_volume : sliced_volumes)
|
||||||
if (sliced_volume.region_id == region_id && ! sliced_volume.expolygons_by_layer[layer_id].empty()) {
|
if (sliced_volume.region_id == region_id && ! sliced_volume.expolygons_by_layer.empty() && ! sliced_volume.expolygons_by_layer[layer_id].empty()) {
|
||||||
++ num_volumes;
|
++ num_volumes;
|
||||||
append(expolygons, std::move(sliced_volume.expolygons_by_layer[layer_id]));
|
append(expolygons, std::move(sliced_volume.expolygons_by_layer[layer_id]));
|
||||||
}
|
}
|
||||||
|
@ -2140,7 +2154,7 @@ std::string PrintObject::_fix_slicing_errors()
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Slicing objects - fixing slicing errors in parallel - end";
|
BOOST_LOG_TRIVIAL(debug) << "Slicing objects - fixing slicing errors in parallel - end";
|
||||||
|
|
||||||
// remove empty layers from bottom
|
// remove empty layers from bottom
|
||||||
while (! m_layers.empty() && m_layers.front()->slices.expolygons.empty()) {
|
while (! m_layers.empty() && m_layers.front()->slices.empty()) {
|
||||||
delete m_layers.front();
|
delete m_layers.front();
|
||||||
m_layers.erase(m_layers.begin());
|
m_layers.erase(m_layers.begin());
|
||||||
m_layers.front()->lower_layer = nullptr;
|
m_layers.front()->lower_layer = nullptr;
|
||||||
|
@ -2167,115 +2181,17 @@ void PrintObject::_simplify_slices(double distance)
|
||||||
Layer *layer = m_layers[layer_idx];
|
Layer *layer = m_layers[layer_idx];
|
||||||
for (size_t region_idx = 0; region_idx < layer->m_regions.size(); ++ region_idx)
|
for (size_t region_idx = 0; region_idx < layer->m_regions.size(); ++ region_idx)
|
||||||
layer->m_regions[region_idx]->slices.simplify(distance);
|
layer->m_regions[region_idx]->slices.simplify(distance);
|
||||||
layer->slices.simplify(distance);
|
{
|
||||||
|
ExPolygons simplified;
|
||||||
|
for (const ExPolygon& expoly : layer->slices)
|
||||||
|
expoly.simplify(distance, &simplified);
|
||||||
|
layer->slices = std::move(simplified);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Slicing objects - siplifying slices in parallel - end";
|
BOOST_LOG_TRIVIAL(debug) << "Slicing objects - siplifying slices in parallel - end";
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintObject::_make_perimeters()
|
|
||||||
{
|
|
||||||
if (! this->set_started(posPerimeters))
|
|
||||||
return;
|
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(info) << "Generating perimeters..." << log_memory_info();
|
|
||||||
|
|
||||||
// merge slices if they were split into types
|
|
||||||
if (this->typed_slices) {
|
|
||||||
for (Layer *layer : m_layers)
|
|
||||||
layer->merge_slices();
|
|
||||||
this->typed_slices = false;
|
|
||||||
this->invalidate_step(posPrepareInfill);
|
|
||||||
}
|
|
||||||
|
|
||||||
// compare each layer to the one below, and mark those slices needing
|
|
||||||
// one additional inner perimeter, like the top of domed objects-
|
|
||||||
|
|
||||||
// this algorithm makes sure that at least one perimeter is overlapping
|
|
||||||
// but we don't generate any extra perimeter if fill density is zero, as they would be floating
|
|
||||||
// inside the object - infill_only_where_needed should be the method of choice for printing
|
|
||||||
// hollow objects
|
|
||||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
|
|
||||||
const PrintRegion ®ion = *m_print->regions()[region_id];
|
|
||||||
if (! region.config().extra_perimeters || region.config().perimeters == 0 || region.config().fill_density == 0 || this->layer_count() < 2)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - start";
|
|
||||||
tbb::parallel_for(
|
|
||||||
tbb::blocked_range<size_t>(0, m_layers.size() - 1),
|
|
||||||
[this, ®ion, region_id](const tbb::blocked_range<size_t>& range) {
|
|
||||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
|
||||||
LayerRegion &layerm = *m_layers[layer_idx]->regions()[region_id];
|
|
||||||
const LayerRegion &upper_layerm = *m_layers[layer_idx+1]->regions()[region_id];
|
|
||||||
const Polygons upper_layerm_polygons = upper_layerm.slices;
|
|
||||||
// Filter upper layer polygons in intersection_ppl by their bounding boxes?
|
|
||||||
// my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ];
|
|
||||||
const double total_loop_length = total_length(upper_layerm_polygons);
|
|
||||||
const coord_t perimeter_spacing = layerm.flow(frPerimeter).scaled_spacing();
|
|
||||||
const Flow ext_perimeter_flow = layerm.flow(frExternalPerimeter);
|
|
||||||
const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width();
|
|
||||||
const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing();
|
|
||||||
|
|
||||||
for (Surface &slice : layerm.slices.surfaces) {
|
|
||||||
for (;;) {
|
|
||||||
// compute the total thickness of perimeters
|
|
||||||
const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2
|
|
||||||
+ (region.config().perimeters-1 + slice.extra_perimeters) * perimeter_spacing;
|
|
||||||
// define a critical area where we don't want the upper slice to fall into
|
|
||||||
// (it should either lay over our perimeters or outside this area)
|
|
||||||
const coord_t critical_area_depth = coord_t(perimeter_spacing * 1.5);
|
|
||||||
const Polygons critical_area = diff(
|
|
||||||
offset(slice.expolygon, float(- perimeters_thickness)),
|
|
||||||
offset(slice.expolygon, float(- perimeters_thickness - critical_area_depth))
|
|
||||||
);
|
|
||||||
// check whether a portion of the upper slices falls inside the critical area
|
|
||||||
const Polylines intersection = intersection_pl(to_polylines(upper_layerm_polygons), critical_area);
|
|
||||||
// only add an additional loop if at least 30% of the slice loop would benefit from it
|
|
||||||
if (total_length(intersection) <= total_loop_length*0.3)
|
|
||||||
break;
|
|
||||||
/*
|
|
||||||
if (0) {
|
|
||||||
require "Slic3r/SVG.pm";
|
|
||||||
Slic3r::SVG::output(
|
|
||||||
"extra.svg",
|
|
||||||
no_arrows => 1,
|
|
||||||
expolygons => union_ex($critical_area),
|
|
||||||
polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
++ slice.extra_perimeters;
|
|
||||||
}
|
|
||||||
#ifdef DEBUG
|
|
||||||
if (slice.extra_perimeters > 0)
|
|
||||||
printf(" adding %d more perimeter(s) at layer %zu\n", slice.extra_perimeters, layer_idx);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - end";
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - start";
|
|
||||||
tbb::parallel_for(
|
|
||||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
|
||||||
[this](const tbb::blocked_range<size_t>& range) {
|
|
||||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
|
|
||||||
m_layers[layer_idx]->make_perimeters();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end";
|
|
||||||
|
|
||||||
/*
|
|
||||||
simplify slices (both layer and region slices),
|
|
||||||
we only need the max resolution for perimeters
|
|
||||||
### This makes this method not-idempotent, so we keep it disabled for now.
|
|
||||||
###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION);
|
|
||||||
*/
|
|
||||||
|
|
||||||
this->set_done(posPerimeters);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only active if config->infill_only_where_needed. This step trims the sparse infill,
|
// Only active if config->infill_only_where_needed. This step trims the sparse infill,
|
||||||
// so it acts as an internal support. It maintains all other infill types intact.
|
// so it acts as an internal support. It maintains all other infill types intact.
|
||||||
// Here the internal surfaces and perimeters have to be supported by the sparse infill.
|
// Here the internal surfaces and perimeters have to be supported by the sparse infill.
|
||||||
|
@ -2301,7 +2217,7 @@ void PrintObject::clip_fill_surfaces()
|
||||||
// Detect things that we need to support.
|
// Detect things that we need to support.
|
||||||
// Cummulative slices.
|
// Cummulative slices.
|
||||||
Polygons slices;
|
Polygons slices;
|
||||||
polygons_append(slices, layer->slices.expolygons);
|
polygons_append(slices, layer->slices);
|
||||||
// Cummulative fill surfaces.
|
// Cummulative fill surfaces.
|
||||||
Polygons fill_surfaces;
|
Polygons fill_surfaces;
|
||||||
// Solid surfaces to be supported.
|
// Solid surfaces to be supported.
|
||||||
|
|
|
@ -445,8 +445,8 @@ Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_t
|
||||||
Polygons collect_slices_outer(const Layer &layer)
|
Polygons collect_slices_outer(const Layer &layer)
|
||||||
{
|
{
|
||||||
Polygons out;
|
Polygons out;
|
||||||
out.reserve(out.size() + layer.slices.expolygons.size());
|
out.reserve(out.size() + layer.slices.size());
|
||||||
for (const ExPolygon &expoly : layer.slices.expolygons)
|
for (const ExPolygon &expoly : layer.slices)
|
||||||
out.emplace_back(expoly.contour);
|
out.emplace_back(expoly.contour);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
@ -907,9 +907,13 @@ namespace SupportMaterialInternal {
|
||||||
polyline.extend_start(fw);
|
polyline.extend_start(fw);
|
||||||
polyline.extend_end(fw);
|
polyline.extend_end(fw);
|
||||||
// Is the straight perimeter segment supported at both sides?
|
// Is the straight perimeter segment supported at both sides?
|
||||||
if (lower_layer.slices.contains(polyline.first_point()) && lower_layer.slices.contains(polyline.last_point()))
|
for (size_t i = 0; i < lower_layer.slices.size(); ++ i)
|
||||||
|
if (lower_layer.slices_bboxes[i].contains(polyline.first_point()) && lower_layer.slices_bboxes[i].contains(polyline.last_point()) &&
|
||||||
|
lower_layer.slices[i].contains(polyline.first_point()) && lower_layer.slices[i].contains(polyline.last_point())) {
|
||||||
// Offset a polyline into a thick line.
|
// Offset a polyline into a thick line.
|
||||||
polygons_append(bridges, offset(polyline, 0.5f * w + 10.f));
|
polygons_append(bridges, offset(polyline, 0.5f * w + 10.f));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bridges = union_(bridges);
|
bridges = union_(bridges);
|
||||||
}
|
}
|
||||||
|
@ -994,7 +998,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
||||||
// inflate the polygons over and over.
|
// inflate the polygons over and over.
|
||||||
Polygons &covered = buildplate_covered[layer_id];
|
Polygons &covered = buildplate_covered[layer_id];
|
||||||
covered = buildplate_covered[layer_id - 1];
|
covered = buildplate_covered[layer_id - 1];
|
||||||
polygons_append(covered, offset(lower_layer.slices.expolygons, scale_(0.01)));
|
polygons_append(covered, offset(lower_layer.slices, scale_(0.01)));
|
||||||
covered = union_(covered, false); // don't apply the safety offset.
|
covered = union_(covered, false); // don't apply the safety offset.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1023,7 +1027,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
||||||
Polygons contact_polygons;
|
Polygons contact_polygons;
|
||||||
Polygons slices_margin_cached;
|
Polygons slices_margin_cached;
|
||||||
float slices_margin_cached_offset = -1.;
|
float slices_margin_cached_offset = -1.;
|
||||||
Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id-1]->slices.expolygons);
|
Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id-1]->slices);
|
||||||
// Offset of the lower layer, to trim the support polygons with to calculate dense supports.
|
// Offset of the lower layer, to trim the support polygons with to calculate dense supports.
|
||||||
float no_interface_offset = 0.f;
|
float no_interface_offset = 0.f;
|
||||||
if (layer_id == 0) {
|
if (layer_id == 0) {
|
||||||
|
@ -1162,7 +1166,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
||||||
slices_margin_cached_offset = slices_margin_offset;
|
slices_margin_cached_offset = slices_margin_offset;
|
||||||
slices_margin_cached = (slices_margin_offset == 0.f) ?
|
slices_margin_cached = (slices_margin_offset == 0.f) ?
|
||||||
lower_layer_polygons :
|
lower_layer_polygons :
|
||||||
offset2(to_polygons(lower_layer.slices.expolygons), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
offset2(to_polygons(lower_layer.slices), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
||||||
if (! buildplate_covered.empty()) {
|
if (! buildplate_covered.empty()) {
|
||||||
// Trim the inflated contact surfaces by the top surfaces as well.
|
// Trim the inflated contact surfaces by the top surfaces as well.
|
||||||
polygons_append(slices_margin_cached, buildplate_covered[layer_id]);
|
polygons_append(slices_margin_cached, buildplate_covered[layer_id]);
|
||||||
|
@ -1468,7 +1472,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
|
||||||
svg.draw(union_ex(top, false), "blue", 0.5f);
|
svg.draw(union_ex(top, false), "blue", 0.5f);
|
||||||
svg.draw(union_ex(projection_raw, true), "red", 0.5f);
|
svg.draw(union_ex(projection_raw, true), "red", 0.5f);
|
||||||
svg.draw_outline(union_ex(projection_raw, true), "red", "blue", scale_(0.1f));
|
svg.draw_outline(union_ex(projection_raw, true), "red", "blue", scale_(0.1f));
|
||||||
svg.draw(layer.slices.expolygons, "green", 0.5f);
|
svg.draw(layer.slices, "green", 0.5f);
|
||||||
}
|
}
|
||||||
#endif /* SLIC3R_DEBUG */
|
#endif /* SLIC3R_DEBUG */
|
||||||
|
|
||||||
|
@ -1568,8 +1572,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
|
||||||
Polygons &layer_support_area = layer_support_areas[layer_id];
|
Polygons &layer_support_area = layer_support_areas[layer_id];
|
||||||
task_group.run([this, &projection, &projection_raw, &layer, &layer_support_area, layer_id] {
|
task_group.run([this, &projection, &projection_raw, &layer, &layer_support_area, layer_id] {
|
||||||
// Remove the areas that touched from the projection that will continue on next, lower, top surfaces.
|
// Remove the areas that touched from the projection that will continue on next, lower, top surfaces.
|
||||||
// Polygons trimming = union_(to_polygons(layer.slices.expolygons), touching, true);
|
// Polygons trimming = union_(to_polygons(layer.slices), touching, true);
|
||||||
Polygons trimming = offset(layer.slices.expolygons, float(SCALED_EPSILON));
|
Polygons trimming = offset(layer.slices, float(SCALED_EPSILON));
|
||||||
projection = diff(projection_raw, trimming, false);
|
projection = diff(projection_raw, trimming, false);
|
||||||
#ifdef SLIC3R_DEBUG
|
#ifdef SLIC3R_DEBUG
|
||||||
{
|
{
|
||||||
|
@ -2101,7 +2105,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
|
||||||
const Layer &object_layer = *object.layers()[i];
|
const Layer &object_layer = *object.layers()[i];
|
||||||
if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON)
|
if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON)
|
||||||
break;
|
break;
|
||||||
polygons_append(polygons_trimming, offset(object_layer.slices.expolygons, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
polygons_append(polygons_trimming, offset(object_layer.slices, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||||
}
|
}
|
||||||
if (! m_slicing_params.soluble_interface) {
|
if (! m_slicing_params.soluble_interface) {
|
||||||
// Collect all bottom surfaces, which will be extruded with a bridging flow.
|
// Collect all bottom surfaces, which will be extruded with a bridging flow.
|
||||||
|
@ -2214,7 +2218,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
|
||||||
// Expand the bases of the support columns in the 1st layer.
|
// Expand the bases of the support columns in the 1st layer.
|
||||||
columns_base->polygons = diff(
|
columns_base->polygons = diff(
|
||||||
offset(columns_base->polygons, inflate_factor_1st_layer),
|
offset(columns_base->polygons, inflate_factor_1st_layer),
|
||||||
offset(m_object->layers().front()->slices.expolygons, (float)scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
offset(m_object->layers().front()->slices, (float)scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||||
if (contacts != nullptr)
|
if (contacts != nullptr)
|
||||||
columns_base->polygons = diff(columns_base->polygons, interface_polygons);
|
columns_base->polygons = diff(columns_base->polygons, interface_polygons);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1717,13 +1717,18 @@ static void thick_point_to_verts(const Vec3crd& point,
|
||||||
point_to_indexed_vertex_array(point, width, height, volume.indexed_vertex_array);
|
point_to_indexed_vertex_array(point, width, height, volume.indexed_vertex_array);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _3DScene::extrusionentity_to_verts(const Polyline &polyline, float width, float height, float print_z, GLVolume& volume)
|
||||||
|
{
|
||||||
|
if (polyline.size() >= 2) {
|
||||||
|
size_t num_segments = polyline.size() - 1;
|
||||||
|
thick_lines_to_verts(polyline.lines(), std::vector<double>(num_segments, width), std::vector<double>(num_segments, height), false, print_z, volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fill in the qverts and tverts with quads and triangles for the extrusion_path.
|
// Fill in the qverts and tverts with quads and triangles for the extrusion_path.
|
||||||
void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, GLVolume &volume)
|
void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, GLVolume &volume)
|
||||||
{
|
{
|
||||||
Lines lines = extrusion_path.polyline.lines();
|
extrusionentity_to_verts(extrusion_path.polyline, extrusion_path.width, extrusion_path.height, print_z, volume);
|
||||||
std::vector<double> widths(lines.size(), extrusion_path.width);
|
|
||||||
std::vector<double> heights(lines.size(), extrusion_path.height);
|
|
||||||
thick_lines_to_verts(lines, widths, heights, false, print_z, volume);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill in the qverts and tverts with quads and triangles for the extrusion_path.
|
// Fill in the qverts and tverts with quads and triangles for the extrusion_path.
|
||||||
|
|
|
@ -656,6 +656,7 @@ public:
|
||||||
|
|
||||||
static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GLVolume& volume);
|
static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GLVolume& volume);
|
||||||
static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GLVolume& volume);
|
static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GLVolume& volume);
|
||||||
|
static void extrusionentity_to_verts(const Polyline &polyline, float width, float height, float print_z, GLVolume& volume);
|
||||||
static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume);
|
static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume);
|
||||||
static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume);
|
static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume);
|
||||||
static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume);
|
static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume);
|
||||||
|
|
|
@ -354,8 +354,7 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
|
||||||
toggle_field("pad_wall_slope", pad_en);
|
toggle_field("pad_wall_slope", pad_en);
|
||||||
toggle_field("pad_around_object", pad_en);
|
toggle_field("pad_around_object", pad_en);
|
||||||
|
|
||||||
bool has_suppad = pad_en && supports_en;
|
bool zero_elev = config->opt_bool("pad_around_object") && pad_en;
|
||||||
bool zero_elev = config->opt_bool("pad_around_object") && has_suppad;
|
|
||||||
|
|
||||||
toggle_field("support_object_elevation", supports_en && !zero_elev);
|
toggle_field("support_object_elevation", supports_en && !zero_elev);
|
||||||
toggle_field("pad_object_gap", zero_elev);
|
toggle_field("pad_object_gap", zero_elev);
|
||||||
|
|
|
@ -5013,13 +5013,13 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
|
||||||
// helper functions to select data in dependence of the extrusion view type
|
// helper functions to select data in dependence of the extrusion view type
|
||||||
struct Helper
|
struct Helper
|
||||||
{
|
{
|
||||||
static float path_filter(GCodePreviewData::Extrusion::EViewType type, const ExtrusionPath& path)
|
static float path_filter(GCodePreviewData::Extrusion::EViewType type, const GCodePreviewData::Extrusion::Path& path)
|
||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case GCodePreviewData::Extrusion::FeatureType:
|
case GCodePreviewData::Extrusion::FeatureType:
|
||||||
// The role here is used for coloring.
|
// The role here is used for coloring.
|
||||||
return (float)path.role();
|
return (float)path.extrusion_role;
|
||||||
case GCodePreviewData::Extrusion::Height:
|
case GCodePreviewData::Extrusion::Height:
|
||||||
return path.height;
|
return path.height;
|
||||||
case GCodePreviewData::Extrusion::Width:
|
case GCodePreviewData::Extrusion::Width:
|
||||||
|
@ -5097,15 +5097,15 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
|
||||||
{
|
{
|
||||||
std::vector<size_t> num_paths_per_role(size_t(erCount), 0);
|
std::vector<size_t> num_paths_per_role(size_t(erCount), 0);
|
||||||
for (const GCodePreviewData::Extrusion::Layer &layer : preview_data.extrusion.layers)
|
for (const GCodePreviewData::Extrusion::Layer &layer : preview_data.extrusion.layers)
|
||||||
for (const ExtrusionPath &path : layer.paths)
|
for (const GCodePreviewData::Extrusion::Path &path : layer.paths)
|
||||||
++ num_paths_per_role[size_t(path.role())];
|
++ num_paths_per_role[size_t(path.extrusion_role)];
|
||||||
std::vector<std::vector<float>> roles_values;
|
std::vector<std::vector<float>> roles_values;
|
||||||
roles_values.assign(size_t(erCount), std::vector<float>());
|
roles_values.assign(size_t(erCount), std::vector<float>());
|
||||||
for (size_t i = 0; i < roles_values.size(); ++ i)
|
for (size_t i = 0; i < roles_values.size(); ++ i)
|
||||||
roles_values[i].reserve(num_paths_per_role[i]);
|
roles_values[i].reserve(num_paths_per_role[i]);
|
||||||
for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers)
|
for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers)
|
||||||
for (const ExtrusionPath& path : layer.paths)
|
for (const GCodePreviewData::Extrusion::Path &path : layer.paths)
|
||||||
roles_values[size_t(path.role())].emplace_back(Helper::path_filter(preview_data.extrusion.view_type, path));
|
roles_values[size_t(path.extrusion_role)].emplace_back(Helper::path_filter(preview_data.extrusion.view_type, path));
|
||||||
roles_filters.reserve(size_t(erCount));
|
roles_filters.reserve(size_t(erCount));
|
||||||
size_t num_buffers = 0;
|
size_t num_buffers = 0;
|
||||||
for (std::vector<float> &values : roles_values) {
|
for (std::vector<float> &values : roles_values) {
|
||||||
|
@ -5133,9 +5133,9 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
|
||||||
// populates volumes
|
// populates volumes
|
||||||
for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers)
|
for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers)
|
||||||
{
|
{
|
||||||
for (const ExtrusionPath& path : layer.paths)
|
for (const GCodePreviewData::Extrusion::Path& path : layer.paths)
|
||||||
{
|
{
|
||||||
std::vector<std::pair<float, GLVolume*>> &filters = roles_filters[size_t(path.role())];
|
std::vector<std::pair<float, GLVolume*>> &filters = roles_filters[size_t(path.extrusion_role)];
|
||||||
auto key = std::make_pair<float, GLVolume*>(Helper::path_filter(preview_data.extrusion.view_type, path), nullptr);
|
auto key = std::make_pair<float, GLVolume*>(Helper::path_filter(preview_data.extrusion.view_type, path), nullptr);
|
||||||
auto it_filter = std::lower_bound(filters.begin(), filters.end(), key);
|
auto it_filter = std::lower_bound(filters.begin(), filters.end(), key);
|
||||||
assert(it_filter != filters.end() && key.first == it_filter->first);
|
assert(it_filter != filters.end() && key.first == it_filter->first);
|
||||||
|
@ -5145,7 +5145,7 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
|
||||||
vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
|
vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
|
||||||
vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
|
vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
|
||||||
|
|
||||||
_3DScene::extrusionentity_to_verts(path, layer.z, vol);
|
_3DScene::extrusionentity_to_verts(path.polyline, path.width, path.height, layer.z, vol);
|
||||||
}
|
}
|
||||||
// Ensure that no volume grows over the limits. If the volume is too large, allocate a new one.
|
// Ensure that no volume grows over the limits. If the volume is too large, allocate a new one.
|
||||||
for (std::vector<std::pair<float, GLVolume*>> &filters : roles_filters) {
|
for (std::vector<std::pair<float, GLVolume*>> &filters : roles_filters) {
|
||||||
|
|
|
@ -107,8 +107,8 @@ void GLTexture::Compressor::compress()
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// stb_dxt library, despite claiming that the needed size of the destination buffer is equal to (source buffer size)/4,
|
// stb_dxt library, despite claiming that the needed size of the destination buffer is equal to (source buffer size)/4,
|
||||||
// crashes if doing so, so we start with twice the required size
|
// crashes if doing so, requiring a minimum of 16 bytes and up to a third of the source buffer size, so we set the destination buffer initial size to be half the source buffer size
|
||||||
level.compressed_data = std::vector<unsigned char>(level.w * level.h * 2, 0);
|
level.compressed_data = std::vector<unsigned char>(std::max((unsigned int)16, level.w * level.h * 2), 0);
|
||||||
int compressed_size = 0;
|
int compressed_size = 0;
|
||||||
rygCompress(level.compressed_data.data(), level.src_data.data(), level.w, level.h, 1, compressed_size);
|
rygCompress(level.compressed_data.data(), level.src_data.data(), level.w, level.h, 1, compressed_size);
|
||||||
level.compressed_data.resize(compressed_size);
|
level.compressed_data.resize(compressed_size);
|
||||||
|
@ -455,8 +455,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo
|
||||||
int lod_w = m_width;
|
int lod_w = m_width;
|
||||||
int lod_h = m_height;
|
int lod_h = m_height;
|
||||||
GLint level = 0;
|
GLint level = 0;
|
||||||
// we do not need to generate all levels down to 1x1
|
while ((lod_w > 1) || (lod_h > 1))
|
||||||
while ((lod_w > 16) || (lod_h > 16))
|
|
||||||
{
|
{
|
||||||
++level;
|
++level;
|
||||||
|
|
||||||
|
@ -600,8 +599,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo
|
||||||
int lod_w = m_width;
|
int lod_w = m_width;
|
||||||
int lod_h = m_height;
|
int lod_h = m_height;
|
||||||
GLint level = 0;
|
GLint level = 0;
|
||||||
// we do not need to generate all levels down to 1x1
|
while ((lod_w > 1) || (lod_h > 1))
|
||||||
while ((lod_w > 16) || (lod_h > 16))
|
|
||||||
{
|
{
|
||||||
++level;
|
++level;
|
||||||
|
|
||||||
|
|
|
@ -1808,7 +1808,10 @@ void TabPrinter::build_fff()
|
||||||
optgroup->append_single_option_line("single_extruder_multi_material");
|
optgroup->append_single_option_line("single_extruder_multi_material");
|
||||||
|
|
||||||
optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) {
|
optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) {
|
||||||
size_t extruders_count = boost::any_cast<size_t>(optgroup->get_value("extruders_count"));
|
// optgroup->get_value() return int for def.type == coInt,
|
||||||
|
// Thus, there should be boost::any_cast<int> !
|
||||||
|
// Otherwise, boost::any_cast<size_t> causes an "unhandled unknown exception"
|
||||||
|
size_t extruders_count = size_t(boost::any_cast<int>(optgroup->get_value("extruders_count")));
|
||||||
wxTheApp->CallAfter([this, opt_key, value, extruders_count]() {
|
wxTheApp->CallAfter([this, opt_key, value, extruders_count]() {
|
||||||
if (opt_key == "extruders_count" || opt_key == "single_extruder_multi_material") {
|
if (opt_key == "extruders_count" || opt_key == "single_extruder_multi_material") {
|
||||||
extruders_count_changed(extruders_count);
|
extruders_count_changed(extruders_count);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
%{
|
%{
|
||||||
#include <xsinit.h>
|
#include <xsinit.h>
|
||||||
#include "libslic3r/Layer.hpp"
|
#include "libslic3r/Layer.hpp"
|
||||||
|
#include "libslic3r/ExPolygonCollection.hpp"
|
||||||
%}
|
%}
|
||||||
|
|
||||||
%name{Slic3r::Layer::Region} class LayerRegion {
|
%name{Slic3r::Layer::Region} class LayerRegion {
|
||||||
|
@ -59,8 +60,8 @@
|
||||||
Ref<LayerRegion> get_region(int idx);
|
Ref<LayerRegion> get_region(int idx);
|
||||||
Ref<LayerRegion> add_region(PrintRegion* print_region);
|
Ref<LayerRegion> add_region(PrintRegion* print_region);
|
||||||
|
|
||||||
Ref<ExPolygonCollection> slices()
|
ExPolygonCollection* slices()
|
||||||
%code%{ RETVAL = &THIS->slices; %};
|
%code%{ RETVAL = new ExPolygonCollection(THIS->slices); %};
|
||||||
|
|
||||||
int ptr()
|
int ptr()
|
||||||
%code%{ RETVAL = (int)(intptr_t)THIS; %};
|
%code%{ RETVAL = (int)(intptr_t)THIS; %};
|
||||||
|
@ -108,8 +109,8 @@
|
||||||
Ref<LayerRegion> get_region(int idx);
|
Ref<LayerRegion> get_region(int idx);
|
||||||
Ref<LayerRegion> add_region(PrintRegion* print_region);
|
Ref<LayerRegion> add_region(PrintRegion* print_region);
|
||||||
|
|
||||||
Ref<ExPolygonCollection> slices()
|
ExPolygonCollection* slices()
|
||||||
%code%{ RETVAL = &THIS->slices; %};
|
%code%{ RETVAL = new ExPolygonCollection(THIS->slices); %};
|
||||||
|
|
||||||
void export_region_slices_to_svg(const char *path);
|
void export_region_slices_to_svg(const char *path);
|
||||||
void export_region_fill_surfaces_to_svg(const char *path);
|
void export_region_fill_surfaces_to_svg(const char *path);
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
~PerimeterGenerator();
|
~PerimeterGenerator();
|
||||||
|
|
||||||
void set_lower_slices(ExPolygonCollection* lower_slices)
|
void set_lower_slices(ExPolygonCollection* lower_slices)
|
||||||
%code{% THIS->lower_slices = lower_slices; %};
|
%code{% THIS->lower_slices = &lower_slices->expolygons; %};
|
||||||
void set_layer_id(int layer_id)
|
void set_layer_id(int layer_id)
|
||||||
%code{% THIS->layer_id = layer_id; %};
|
%code{% THIS->layer_id = layer_id; %};
|
||||||
void set_perimeter_flow(Flow* flow)
|
void set_perimeter_flow(Flow* flow)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue