Add functional nonplanar code ported from https://github.com/DrEricEbert/Slic3r_NonPlanar_Slicing
This commit is contained in:
parent
e0f7263a4c
commit
22c42b9844
@ -221,6 +221,10 @@ set(SLIC3R_SOURCES
|
||||
MutablePriorityQueue.hpp
|
||||
NormalUtils.cpp
|
||||
NormalUtils.hpp
|
||||
NonplanarFacet.cpp
|
||||
NonplanarFacet.hpp
|
||||
NonplanarSurface.cpp
|
||||
NonplanarSurface.hpp
|
||||
NSVGUtils.cpp
|
||||
NSVGUtils.hpp
|
||||
ObjectID.cpp
|
||||
|
@ -418,7 +418,7 @@ bool has_duplicate_points(const ExPolygons &expolys)
|
||||
{
|
||||
#if 1
|
||||
// Check globally.
|
||||
#if 0
|
||||
#if 1
|
||||
// Detect duplicates by sorting with quicksort. It is quite fast, but ankerl::unordered_dense is around 1/4 faster.
|
||||
Points allpts;
|
||||
allpts.reserve(count_points(expolys));
|
||||
|
@ -10,6 +10,8 @@
|
||||
#include <string_view>
|
||||
#include <numeric>
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
@ -67,6 +69,8 @@ public:
|
||||
float width;
|
||||
// Height of the extrusion, used for visualization purposes.
|
||||
float height;
|
||||
/// distance to surface layer in nonplanar extrusions -1.0 if not part of nonplanar extrusion
|
||||
float distance_to_top = -1.0;
|
||||
|
||||
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), m_role(role) {}
|
||||
@ -122,6 +126,11 @@ private:
|
||||
ExtrusionRole m_role;
|
||||
};
|
||||
|
||||
class ExtrusionPath3 : public ExtrusionPath
|
||||
{
|
||||
Polyline3 polyline;
|
||||
};
|
||||
|
||||
class ExtrusionPathOriented : public ExtrusionPath
|
||||
{
|
||||
public:
|
||||
|
@ -1,5 +1,7 @@
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "ShortestPath.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
@ -152,4 +154,17 @@ double ExtrusionEntityCollection::min_mm3_per_mm() const
|
||||
return min_mm3_per_mm;
|
||||
}
|
||||
|
||||
void ExtrusionEntityCollection::export_to_svg(const char *path) const
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionEntity *entity : this->entities)
|
||||
bbox.merge(get_extents(entity->as_polylines()));
|
||||
|
||||
SVG svg(path, bbox);
|
||||
for (const ExtrusionEntity *entity : this->entities)
|
||||
svg.draw(entity->as_polylines(), "black", scale_(0.1f));
|
||||
|
||||
svg.Close();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -124,6 +124,7 @@ public:
|
||||
ExtrusionEntityCollection flatten(bool preserve_ordering = false) const;
|
||||
double min_mm3_per_mm() const override;
|
||||
double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; }
|
||||
void export_to_svg(const char *path) const;
|
||||
|
||||
// Following methods shall never be called on an ExtrusionEntityCollection.
|
||||
Polyline as_polyline() const override {
|
||||
|
@ -19,7 +19,9 @@ GCodeExtrusionRole extrusion_role_to_gcode_extrusion_role(ExtrusionRole role)
|
||||
}
|
||||
if (role == ExtrusionRole::InternalInfill) return GCodeExtrusionRole::InternalInfill;
|
||||
if (role == ExtrusionRole::SolidInfill) return GCodeExtrusionRole::SolidInfill;
|
||||
if (role == ExtrusionRole::SolidInfillNonplanar) return GCodeExtrusionRole::SolidInfillNonplanar;
|
||||
if (role == ExtrusionRole::TopSolidInfill) return GCodeExtrusionRole::TopSolidInfill;
|
||||
if (role == ExtrusionRole::TopSolidInfillNonplanar) return GCodeExtrusionRole::TopSolidInfillNonplanar;
|
||||
if (role == ExtrusionRole::Ironing) return GCodeExtrusionRole::Ironing;
|
||||
if (role == ExtrusionRole::BridgeInfill) return GCodeExtrusionRole::BridgeInfill;
|
||||
if (role == ExtrusionRole::GapFill) return GCodeExtrusionRole::GapFill;
|
||||
@ -40,7 +42,9 @@ std::string gcode_extrusion_role_to_string(GCodeExtrusionRole role)
|
||||
case GCodeExtrusionRole::OverhangPerimeter : return L("Overhang perimeter");
|
||||
case GCodeExtrusionRole::InternalInfill : return L("Internal infill");
|
||||
case GCodeExtrusionRole::SolidInfill : return L("Solid infill");
|
||||
case GCodeExtrusionRole::SolidInfillNonplanar : return L("Nonplanar solid infill");
|
||||
case GCodeExtrusionRole::TopSolidInfill : return L("Top solid infill");
|
||||
case GCodeExtrusionRole::TopSolidInfillNonplanar : return L("Nonplanar top solid infill");
|
||||
case GCodeExtrusionRole::Ironing : return L("Ironing");
|
||||
case GCodeExtrusionRole::BridgeInfill : return L("Bridge infill");
|
||||
case GCodeExtrusionRole::GapFill : return L("Gap fill");
|
||||
@ -66,8 +70,12 @@ GCodeExtrusionRole string_to_gcode_extrusion_role(const std::string_view role)
|
||||
return GCodeExtrusionRole::InternalInfill;
|
||||
else if (role == L("Solid infill"))
|
||||
return GCodeExtrusionRole::SolidInfill;
|
||||
else if (role == L("Nonplanar solid infill"))
|
||||
return GCodeExtrusionRole::SolidInfill;
|
||||
else if (role == L("Top solid infill"))
|
||||
return GCodeExtrusionRole::TopSolidInfill;
|
||||
else if (role == L("Nonplanar top solid infill"))
|
||||
return GCodeExtrusionRole::TopSolidInfillNonplanar;
|
||||
else if (role == L("Ironing"))
|
||||
return GCodeExtrusionRole::Ironing;
|
||||
else if (role == L("Bridge infill"))
|
||||
|
@ -26,6 +26,7 @@ enum class ExtrusionRoleModifier : uint16_t {
|
||||
Solid,
|
||||
Ironing,
|
||||
Bridge,
|
||||
Nonplanar,
|
||||
// 3) Special types
|
||||
// Indicator that the extrusion role was mixed from multiple differing extrusion roles,
|
||||
// for example from Support and SupportInterface.
|
||||
@ -55,9 +56,11 @@ struct ExtrusionRole : public ExtrusionRoleModifiers
|
||||
static constexpr const ExtrusionRoleModifiers InternalInfill{ ExtrusionRoleModifier::Infill };
|
||||
// Solid internal infill.
|
||||
static constexpr const ExtrusionRoleModifiers SolidInfill{ ExtrusionRoleModifier::Infill | ExtrusionRoleModifier::Solid };
|
||||
static constexpr const ExtrusionRoleModifiers SolidInfillNonplanar{ ExtrusionRoleModifier::Infill | ExtrusionRoleModifier::Solid | ExtrusionRoleModifier::Nonplanar };
|
||||
// Top solid infill (visible).
|
||||
//FIXME why there is no bottom solid infill type?
|
||||
static constexpr const ExtrusionRoleModifiers TopSolidInfill{ ExtrusionRoleModifier::Infill | ExtrusionRoleModifier::Solid | ExtrusionRoleModifier::External };
|
||||
static constexpr const ExtrusionRoleModifiers TopSolidInfillNonplanar{ ExtrusionRoleModifier::Infill | ExtrusionRoleModifier::Solid | ExtrusionRoleModifier::External | ExtrusionRoleModifier::Nonplanar };
|
||||
// Ironing infill at the top surfaces.
|
||||
static constexpr const ExtrusionRoleModifiers Ironing{ ExtrusionRoleModifier::Infill | ExtrusionRoleModifier::Solid | ExtrusionRoleModifier::Ironing | ExtrusionRoleModifier::External };
|
||||
// Visible bridging infill at the bottom of an object.
|
||||
@ -84,6 +87,7 @@ struct ExtrusionRole : public ExtrusionRoleModifiers
|
||||
bool is_solid_infill() const { return this->is_infill() && this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Solid); }
|
||||
bool is_external() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::External); }
|
||||
bool is_bridge() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Bridge); }
|
||||
bool is_nonplanar() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Nonplanar); }
|
||||
|
||||
bool is_support() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Support); }
|
||||
bool is_support_base() const { return this->is_support() && ! this->is_external(); }
|
||||
@ -108,7 +112,9 @@ enum class GCodeExtrusionRole : uint8_t {
|
||||
OverhangPerimeter,
|
||||
InternalInfill,
|
||||
SolidInfill,
|
||||
SolidInfillNonplanar,
|
||||
TopSolidInfill,
|
||||
TopSolidInfillNonplanar,
|
||||
Ironing,
|
||||
BridgeInfill,
|
||||
GapFill,
|
||||
|
@ -2,6 +2,8 @@
|
||||
#include <stdio.h>
|
||||
#include <memory>
|
||||
|
||||
#include "SVG.hpp"
|
||||
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#include "../Layer.hpp"
|
||||
@ -144,7 +146,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
||||
if (surface.is_solid()) {
|
||||
params.density = 100.f;
|
||||
//FIXME for non-thick bridges, shall we allow a bottom surface pattern?
|
||||
params.pattern = (surface.is_external() && ! is_bridge) ?
|
||||
params.pattern = (surface.is_external() && ! is_bridge && !surface.is_nonplanar()) ?
|
||||
(surface.is_top() ? region_config.top_fill_pattern.value : region_config.bottom_fill_pattern.value) :
|
||||
fill_type_monotonic(region_config.top_fill_pattern) ? ipMonotonic : ipRectilinear;
|
||||
} else if (params.density <= 0)
|
||||
@ -154,7 +156,10 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
||||
is_bridge ?
|
||||
ExtrusionRole::BridgeInfill :
|
||||
(surface.is_solid() ?
|
||||
(surface.is_top() ? ExtrusionRole::TopSolidInfill : ExtrusionRole::SolidInfill) :
|
||||
(surface.is_top() ?
|
||||
(surface.is_nonplanar() ? ExtrusionRole::TopSolidInfillNonplanar : ExtrusionRole::TopSolidInfill) :
|
||||
(surface.is_nonplanar() ? ExtrusionRole::SolidInfillNonplanar : ExtrusionRole::SolidInfill)
|
||||
) :
|
||||
ExtrusionRole::InternalInfill);
|
||||
params.bridge_angle = float(surface.bridge_angle);
|
||||
params.angle = float(Geometry::deg2rad(region_config.fill_angle.value));
|
||||
@ -252,7 +257,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
||||
if (! surface_fill.expolygons.empty()) {
|
||||
distance_between_surfaces = std::max(distance_between_surfaces, surface_fill.params.flow.scaled_spacing());
|
||||
append((surface_fill.surface.surface_type == stInternalVoid) ? voids : surfaces_polygons, to_polygons(surface_fill.expolygons));
|
||||
if (surface_fill.surface.surface_type == stInternalSolid)
|
||||
if (surface_fill.surface.is_internal_solid())
|
||||
region_internal_infill = (int)surface_fill.region_id;
|
||||
if (surface_fill.surface.is_solid())
|
||||
region_solid_infill = (int)surface_fill.region_id;
|
||||
@ -281,7 +286,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
||||
region_id = region_some_infill;
|
||||
const LayerRegion& layerm = *layer.regions()[region_id];
|
||||
for (SurfaceFill &surface_fill : surface_fills)
|
||||
if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height()) < EPSILON) {
|
||||
if ((surface_fill.surface.is_internal_solid()) && std::abs(layer.height - surface_fill.params.flow.height()) < EPSILON) {
|
||||
internal_solid_fill = &surface_fill;
|
||||
break;
|
||||
}
|
||||
@ -309,7 +314,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
||||
// Use ipEnsuring pattern for all internal Solids.
|
||||
{
|
||||
for (size_t surface_fill_id = 0; surface_fill_id < surface_fills.size(); ++surface_fill_id)
|
||||
if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) {
|
||||
if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.is_internal_solid()) {
|
||||
fill.params.pattern = ipEnsuring;
|
||||
}
|
||||
}
|
||||
@ -449,10 +454,12 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_group_fills_to_svg(debug_out_path("Layer-fill_surfaces-10_fill-final-%d.svg", iRun ++).c_str(), surface_fills);
|
||||
export_group_fills_to_svg(debug_out_path("Layer-%d-fill_surfaces-10_fill-final-%d.svg", id(), iRun ++).c_str(), surface_fills);
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "make_fills: found " << surface_fills.size() << " surfaces to fill for layer " << id();
|
||||
|
||||
size_t first_object_layer_id = this->object()->get_layer(0)->id();
|
||||
for (SurfaceFill &surface_fill : surface_fills) {
|
||||
// Create the filler object.
|
||||
@ -519,6 +526,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
else
|
||||
polylines = f->fill_surface(&surface_fill.surface, params);
|
||||
} catch (InfillFailedException &) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "make_fills: InfillFailedException!";
|
||||
}
|
||||
if (!polylines.empty() || !thick_polylines.empty()) {
|
||||
// calculate actual flow from spacing (which might have been adjusted by the infill
|
||||
@ -562,6 +570,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height());
|
||||
}
|
||||
insert_fills_into_islands(*this, uint32_t(surface_fill.region_id), fill_begin, uint32_t(layerm.fills().size()));
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(trace) << "make_fills: no infill was generated for layer " << id();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -854,7 +864,7 @@ void Layer::make_ironing()
|
||||
polygons_append(polys, surface.expolygon);
|
||||
} else {
|
||||
for (const Surface &surface : ironing_params.layerm->slices())
|
||||
if (surface.surface_type == stTop || (iron_everything && surface.surface_type == stBottom))
|
||||
if (surface.is_top() || (iron_everything && surface.surface_type == stBottom))
|
||||
// stBottomBridge is not being ironed on purpose, as it would likely destroy the bridges.
|
||||
polygons_append(polys, surface.expolygon);
|
||||
}
|
||||
@ -862,7 +872,7 @@ void Layer::make_ironing()
|
||||
// Add solid fill surfaces. This may not be ideal, as one will not iron perimeters touching these
|
||||
// solid fill surfaces, but it is likely better than nothing.
|
||||
for (const Surface &surface : ironing_params.layerm->fill_surfaces())
|
||||
if (surface.surface_type == stInternalSolid)
|
||||
if (surface.is_internal_solid())
|
||||
polygons_append(infills, surface.expolygon);
|
||||
}
|
||||
}
|
||||
|
@ -2459,7 +2459,10 @@ void GCode::process_layer_single_object(
|
||||
};
|
||||
|
||||
ExtrusionEntitiesPtr temp_fill_extrusions;
|
||||
if (const Layer *layer = layer_to_print.object_layer; layer)
|
||||
if (const Layer *layer = layer_to_print.object_layer; layer) {
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
layer->export_lslices_polygons_to_svg_debug("process_gcode_layer");
|
||||
#endif
|
||||
for (size_t idx : layer->lslice_indices_sorted_by_print_order) {
|
||||
const LayerSlice &lslice = layer->lslices_ex[idx];
|
||||
auto extrude_infill_range = [&](
|
||||
@ -2472,15 +2475,25 @@ void GCode::process_layer_single_object(
|
||||
for (auto it_fill_range = it_fill_ranges_begin; it_fill_range != it_fill_ranges_end; ++ it_fill_range) {
|
||||
assert(it_fill_range->region() == it_fill_ranges_begin->region());
|
||||
for (uint32_t fill_id : *it_fill_range) {
|
||||
assert(dynamic_cast<ExtrusionEntityCollection*>(fills.entities[fill_id]));
|
||||
if (auto *eec = static_cast<ExtrusionEntityCollection*>(fills.entities[fill_id]);
|
||||
(eec->role() == ExtrusionRole::Ironing) == ironing && shall_print_this_extrusion_collection(eec, region)) {
|
||||
auto *eec = static_cast<ExtrusionEntityCollection*>(fills.entities[fill_id]);
|
||||
assert(eec);
|
||||
if ((eec->role() == ExtrusionRole::Ironing) == ironing && shall_print_this_extrusion_collection(eec, region)) {
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
static int extrude_infill_range_run = 0;
|
||||
eec->export_to_svg(debug_out_path("Layer-%d_gcode-extrude_infill_range-shall-be-printed-%u.svg", layerm.layer()->id(), extrude_infill_range_run++).c_str());
|
||||
#endif
|
||||
if (eec->can_reverse())
|
||||
// Flatten the infill collection for better path planning.
|
||||
for (auto *ee : eec->entities)
|
||||
for (auto *ee : eec->entities) {
|
||||
temp_fill_extrusions.emplace_back(ee);
|
||||
}
|
||||
else
|
||||
temp_fill_extrusions.emplace_back(eec);
|
||||
} else {
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
static int iRun = 0;
|
||||
eec->export_to_svg(debug_out_path("Layer-%d_gcode-extrude_infill_range-shall-not-be-printed-%u.svg", layerm.layer()->id(), iRun++).c_str());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2491,17 +2504,30 @@ void GCode::process_layer_single_object(
|
||||
// Will parallel access of initial G-code preview to these extrusions while reordering them at backend cause issues?
|
||||
chain_and_reorder_extrusion_entities(temp_fill_extrusions, &m_last_pos);
|
||||
const auto extrusion_name = ironing ? "ironing"sv : "infill"sv;
|
||||
for (const ExtrusionEntity *fill : temp_fill_extrusions)
|
||||
for (const ExtrusionEntity *fill : temp_fill_extrusions) {
|
||||
if (auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill); eec) {
|
||||
for (const ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
|
||||
for (const ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) {
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
static int iRun = 0;
|
||||
eec->export_to_svg(debug_out_path("Layer-%d_gcode-extrude_infill_range-collection-%u.svg", layerm.layer()->id(), iRun++).c_str());
|
||||
#endif
|
||||
gcode += this->extrude_entity(*ee, extrusion_name);
|
||||
} else
|
||||
}
|
||||
} else {
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
static int iRun = 0;
|
||||
export_to_svg(debug_out_path("Layer-%d_gcode-extrude_infill_range-entity-%u.svg", layerm.layer()->id(), iRun++).c_str(),
|
||||
fill->as_polyline(), get_extents(layerm.slices().surfaces), scale_(0.1f));
|
||||
#endif
|
||||
gcode += this->extrude_entity(*fill, extrusion_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//FIXME order islands?
|
||||
// Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511)
|
||||
BOOST_LOG_TRIVIAL(trace) << "Generating GCode for layer " << layer->id() << " - slice " << idx << " has " << lslice.islands.size() << " islands";
|
||||
for (const LayerIsland &island : lslice.islands) {
|
||||
auto process_perimeters = [&]() {
|
||||
const LayerRegion &layerm = *layer->get_region(island.perimeters.region());
|
||||
@ -2510,9 +2536,13 @@ void GCode::process_layer_single_object(
|
||||
const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id());
|
||||
bool first = true;
|
||||
for (uint32_t perimeter_id : island.perimeters) {
|
||||
assert(dynamic_cast<const ExtrusionEntityCollection*>(layerm.perimeters().entities[perimeter_id]));
|
||||
if (const auto *eec = static_cast<const ExtrusionEntityCollection*>(layerm.perimeters().entities[perimeter_id]);
|
||||
shall_print_this_extrusion_collection(eec, region)) {
|
||||
const auto *eec = static_cast<const ExtrusionEntityCollection*>(layerm.perimeters().entities[perimeter_id]);
|
||||
assert(eec);
|
||||
if (shall_print_this_extrusion_collection(eec, region)) {
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
static int iRun = 0;
|
||||
eec->export_to_svg(debug_out_path("Layer-%d_gcode-process_perimeters-%u.svg", layerm.layer()->id(), iRun++).c_str());
|
||||
#endif
|
||||
// This may not apply to Arachne, but maybe the Arachne gap fill should disable reverse as well?
|
||||
// assert(! eec->can_reverse());
|
||||
if (first) {
|
||||
@ -2520,9 +2550,15 @@ void GCode::process_layer_single_object(
|
||||
init_layer_delayed();
|
||||
m_config.apply(region.config());
|
||||
}
|
||||
for (const ExtrusionEntity *ee : *eec)
|
||||
for (const ExtrusionEntity *ee : *eec) {
|
||||
gcode += this->extrude_entity(*ee, comment_perimeter, -1.);
|
||||
}
|
||||
} else {
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
static int iRun = 0;
|
||||
eec->export_to_svg(debug_out_path("Layer-%d_gcode-process_perimeters-not-printed-%u.svg", layerm.layer()->id(), iRun++).c_str());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
};
|
||||
auto process_infill = [&]() {
|
||||
@ -2535,6 +2571,7 @@ void GCode::process_layer_single_object(
|
||||
it = it_end;
|
||||
}
|
||||
};
|
||||
BOOST_LOG_TRIVIAL(trace) << "Generating GCode for layer " << layer->id() << " island - " << island.fills.size() << " fills, " << island.perimeters.size() << " perimeters.";
|
||||
if (print.config().infill_first) {
|
||||
process_infill();
|
||||
process_perimeters();
|
||||
@ -2558,6 +2595,7 @@ void GCode::process_layer_single_object(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! first && this->config().gcode_label_objects)
|
||||
gcode += std::string("; stop printing object ") + print_object.model_object()->name + " id:" + std::to_string(object_id) + " copy " + std::to_string(print_instance.instance_id) + "\n";
|
||||
}
|
||||
@ -2793,6 +2831,11 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, const std::s
|
||||
|
||||
std::string GCode::extrude_entity(const ExtrusionEntity &entity, const std::string_view description, double speed)
|
||||
{
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
static int extrude_infill_range_run = 0;
|
||||
export_to_svg(debug_out_path("gcode-extrude_entity-%u.svg", extrude_infill_range_run++).c_str(),
|
||||
entity.as_polyline(), get_extents(entity.as_polyline()), scale_(0.1f));
|
||||
#endif
|
||||
if (const ExtrusionPath* path = dynamic_cast<const ExtrusionPath*>(&entity))
|
||||
return this->extrude_path(*path, description, speed);
|
||||
else if (const ExtrusionMultiPath* multipath = dynamic_cast<const ExtrusionMultiPath*>(&entity))
|
||||
@ -2943,7 +2986,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
|
||||
acceleration = m_config.first_layer_acceleration_over_raft.value;
|
||||
} else if (m_config.bridge_acceleration.value > 0 && path.role().is_bridge()) {
|
||||
acceleration = m_config.bridge_acceleration.value;
|
||||
} else if (m_config.top_solid_infill_acceleration > 0 && path.role() == ExtrusionRole::TopSolidInfill) {
|
||||
} else if (m_config.top_solid_infill_acceleration > 0 && (path.role() == ExtrusionRole::TopSolidInfill || path.role() == ExtrusionRole::TopSolidInfillNonplanar)) {
|
||||
acceleration = m_config.top_solid_infill_acceleration.value;
|
||||
} else if (m_config.solid_infill_acceleration > 0 && path.role().is_solid_infill()) {
|
||||
acceleration = m_config.solid_infill_acceleration.value;
|
||||
@ -2976,9 +3019,9 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
|
||||
speed = m_config.get_abs_value("bridge_speed");
|
||||
} else if (path.role() == ExtrusionRole::InternalInfill) {
|
||||
speed = m_config.get_abs_value("infill_speed");
|
||||
} else if (path.role() == ExtrusionRole::SolidInfill) {
|
||||
} else if (path.role() == ExtrusionRole::SolidInfill || path.role() == ExtrusionRole::SolidInfillNonplanar) {
|
||||
speed = m_config.get_abs_value("solid_infill_speed");
|
||||
} else if (path.role() == ExtrusionRole::TopSolidInfill) {
|
||||
} else if (path.role() == ExtrusionRole::TopSolidInfill || path.role() == ExtrusionRole::TopSolidInfillNonplanar) {
|
||||
speed = m_config.get_abs_value("top_solid_infill_speed");
|
||||
} else if (path.role() == ExtrusionRole::Ironing) {
|
||||
speed = m_config.get_abs_value("ironing_speed");
|
||||
@ -3118,15 +3161,15 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
|
||||
comment = description;
|
||||
comment += description_bridge;
|
||||
}
|
||||
Vec2d prev = this->point_to_gcode_quantized(path.polyline.points.front());
|
||||
Vec3d prev3 = this->point3_to_gcode_quantized(path.polyline.points.front());
|
||||
auto it = path.polyline.points.begin();
|
||||
auto end = path.polyline.points.end();
|
||||
for (++ it; it != end; ++ it) {
|
||||
Vec2d p = this->point_to_gcode_quantized(*it);
|
||||
const double line_length = (p - prev).norm();
|
||||
Vec3d p3 = this->point3_to_gcode_quantized(*it);
|
||||
const double line_length = (p3 - prev3).norm();
|
||||
path_length += line_length;
|
||||
gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, comment);
|
||||
prev = p;
|
||||
gcode += m_writer.extrude_to_xyz(p3, e_per_mm * line_length, comment);
|
||||
prev3 = p3;
|
||||
}
|
||||
} else {
|
||||
std::string marked_comment;
|
||||
@ -3138,13 +3181,13 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
|
||||
double last_set_fan_speed = new_points[0].fan_speed;
|
||||
gcode += m_writer.set_speed(last_set_speed, "", cooling_marker_setspeed_comments);
|
||||
gcode += "\n;_SET_FAN_SPEED" + std::to_string(int(last_set_fan_speed)) + "\n";
|
||||
Vec2d prev = this->point_to_gcode_quantized(new_points[0].p);
|
||||
Vec3d prev3 = this->point3_to_gcode_quantized(new_points[0].p);
|
||||
for (size_t i = 1; i < new_points.size(); i++) {
|
||||
const ProcessedPoint &processed_point = new_points[i];
|
||||
Vec2d p = this->point_to_gcode_quantized(processed_point.p);
|
||||
const double line_length = (p - prev).norm();
|
||||
gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, marked_comment);
|
||||
prev = p;
|
||||
Vec3d p3 = this->point3_to_gcode_quantized(processed_point.p);
|
||||
const double line_length = (p3 - prev3).norm();
|
||||
gcode += m_writer.extrude_to_xyz(p3, e_per_mm * line_length, marked_comment);
|
||||
prev3 = p3;
|
||||
double new_speed = processed_point.speed * 60.0;
|
||||
if (last_set_speed != new_speed) {
|
||||
gcode += m_writer.set_speed(new_speed, "", cooling_marker_setspeed_comments);
|
||||
@ -3186,6 +3229,8 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string
|
||||
|
||||
// check whether a straight travel move would need retraction
|
||||
bool needs_retraction = this->needs_retraction(travel, role);
|
||||
// check whether we need to move to a different z layer
|
||||
bool needs_zmove = this->needs_zmove(travel);
|
||||
// check whether wipe could be disabled without causing visible stringing
|
||||
bool could_be_wipe_disabled = false;
|
||||
// Save state of use_external_mp_once for the case that will be needed to call twice m_avoid_crossing_perimeters.travel_to.
|
||||
@ -3232,10 +3277,20 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string
|
||||
// use G1 because we rely on paths being straight (G0 may make round paths)
|
||||
if (travel.size() >= 2) {
|
||||
|
||||
// Move Z up if necessary
|
||||
if (needs_zmove) {
|
||||
gcode += m_writer.travel_to_z(this->layer()->print_z, "Move up for non planar extrusion");
|
||||
}
|
||||
|
||||
gcode += m_writer.set_travel_acceleration((unsigned int)(m_config.travel_acceleration.value + 0.5));
|
||||
|
||||
for (size_t i = 1; i < travel.size(); ++ i)
|
||||
for (size_t i = 1; i < travel.size(); ++ i) {
|
||||
if (needs_zmove) {
|
||||
gcode += m_writer.travel_to_xyz(this->point3_to_gcode(travel.points[i]), comment);
|
||||
} else {
|
||||
gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment);
|
||||
}
|
||||
}
|
||||
|
||||
if (! GCodeWriter::supports_separate_travel_acceleration(config().gcode_flavor)) {
|
||||
// In case that this flavor does not support separate print and travel acceleration,
|
||||
@ -3243,8 +3298,18 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string
|
||||
gcode += m_writer.set_travel_acceleration((unsigned int)(m_config.travel_acceleration.value + 0.5));
|
||||
}
|
||||
|
||||
if (needs_zmove) {
|
||||
float move_z = unscale<double>(point.nonplanar_z);
|
||||
if(point.nonplanar_z == -1) {
|
||||
move_z = this->layer()->print_z;
|
||||
}
|
||||
gcode += m_writer.travel_to_z(move_z, "Move down for non planar extrusion");
|
||||
}
|
||||
|
||||
this->set_last_pos(travel.points.back());
|
||||
}
|
||||
|
||||
|
||||
return gcode;
|
||||
}
|
||||
|
||||
@ -3287,6 +3352,24 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GCode::needs_zmove(const Polyline &travel)
|
||||
{
|
||||
if (travel.length() < scale_(1.0)) {
|
||||
// skip zmove if the move is shorter 1 mm
|
||||
return false;
|
||||
}
|
||||
|
||||
//check if any point in travel is below the layer z
|
||||
for (Point p : travel.points)
|
||||
{
|
||||
if ((p.nonplanar_z != -1) && (p.nonplanar_z < scale_(this->layer()->print_z)))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string GCode::retract(bool toolchange)
|
||||
{
|
||||
std::string gcode;
|
||||
@ -3421,12 +3504,28 @@ Vec2d GCode::point_to_gcode(const Point &point) const
|
||||
return unscaled<double>(point) + m_origin - extruder_offset;
|
||||
}
|
||||
|
||||
// convert a model-space scaled point into G-code coordinates
|
||||
Vec3d GCode::point3_to_gcode(const Point &point) const
|
||||
{
|
||||
Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset);
|
||||
double p_x = unscaled<double>(point.x()) + m_origin.x() - extruder_offset.x();
|
||||
double p_y = unscaled<double>(point.y()) + m_origin.y() - extruder_offset.y();
|
||||
double p_z = point.nonplanar_z == -1 ? this->layer()->print_z : unscale<double>(point.nonplanar_z);
|
||||
return { p_x, p_y, p_z };
|
||||
}
|
||||
|
||||
Vec2d GCode::point_to_gcode_quantized(const Point &point) const
|
||||
{
|
||||
Vec2d p = this->point_to_gcode(point);
|
||||
return { GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()) };
|
||||
}
|
||||
|
||||
Vec3d GCode::point3_to_gcode_quantized(const Point &point) const
|
||||
{
|
||||
Vec3d p = this->point3_to_gcode(point);
|
||||
return { GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()), GCodeFormatter::quantize_xyzf(p.z()) };
|
||||
}
|
||||
|
||||
// convert a model-space scaled point into G-code coordinates
|
||||
Point GCode::gcode_to_point(const Vec2d &point) const
|
||||
{
|
||||
@ -3435,7 +3534,6 @@ Point GCode::gcode_to_point(const Vec2d &point) const
|
||||
// This function may be called at the very start from toolchange G-code when the extruder is not assigned yet.
|
||||
pt += m_config.extruder_offset.get_at(extruder->id());
|
||||
return scaled<coord_t>(pt);
|
||||
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -166,8 +166,10 @@ public:
|
||||
const Point& last_pos() const { return m_last_pos; }
|
||||
// Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset.
|
||||
Vec2d point_to_gcode(const Point &point) const;
|
||||
Vec3d point3_to_gcode(const Point &point) const;
|
||||
// Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset and quantized to G-code resolution.
|
||||
Vec2d point_to_gcode_quantized(const Point &point) const;
|
||||
Vec3d point3_to_gcode_quantized(const Point &point) const;
|
||||
Point gcode_to_point(const Vec2d &point) const;
|
||||
const FullPrintConfig &config() const { return m_config; }
|
||||
const Layer* layer() const { return m_layer; }
|
||||
@ -326,6 +328,7 @@ private:
|
||||
|
||||
std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
|
||||
bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None);
|
||||
bool needs_zmove(const Polyline &travel);
|
||||
std::string retract(bool toolchange = false);
|
||||
std::string unretract() { return m_writer.unlift() + m_writer.unretract(); }
|
||||
std::string set_extruder(unsigned int extruder_id, double print_z);
|
||||
|
@ -274,7 +274,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
|
||||
// FIXME: This function was not being used when travel_speed_z was separated (bd6badf).
|
||||
// Calculation of feedrate was not updated accordingly. If you want to use
|
||||
// this function, fix it first.
|
||||
std::terminate();
|
||||
// std::terminate();
|
||||
|
||||
/* If target Z is lower than current Z but higher than nominal Z we
|
||||
don't perform the Z move but we only move in the XY plane and
|
||||
@ -360,16 +360,16 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std:
|
||||
return w.string();
|
||||
}
|
||||
|
||||
#if 0
|
||||
#if 1
|
||||
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment)
|
||||
{
|
||||
m_pos = point;
|
||||
m_lifted = 0;
|
||||
m_extruder->extrude(dE);
|
||||
//m_extruder->extrude(dE);
|
||||
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xyz(point);
|
||||
w.emit_e(m_extrusion_axis, m_extruder->E());
|
||||
w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ public:
|
||||
std::string travel_to_z(double z, const std::string &comment = std::string());
|
||||
bool will_move_z(double z) const;
|
||||
std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string());
|
||||
// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string());
|
||||
std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string());
|
||||
std::string retract(bool before_wipe = false);
|
||||
std::string retract_for_toolchange(bool before_wipe = false);
|
||||
std::string unretract();
|
||||
|
@ -746,4 +746,74 @@ bool trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(const Transform3d &
|
||||
return std::abs(d * d) < EPSILON * lx2 * ly2;
|
||||
}
|
||||
|
||||
bool
|
||||
Point_in_triangle(Vec2f pt, Vec2f v1, Vec2f v2, Vec2f v3)
|
||||
{
|
||||
//Check if point is right of every edge
|
||||
if (sign(pt, v1, v2) <= 0.0f) return false;
|
||||
if (sign(pt, v2, v3) <= 0.0f) return false;
|
||||
if (sign(pt, v3, v1) <= 0.0f) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//https://graphics.stanford.edu/~mdfisher/Code/Engine/Plane.cpp.html
|
||||
coord_t
|
||||
Project_point_on_plane(Vec3f v1, Vec3f n, Point pt)
|
||||
{
|
||||
//if no intersection leave point unchanged (should never happen)
|
||||
if(n.z() == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
//unscale point for calculations
|
||||
float px = unscale<float>(pt.x());
|
||||
float py = unscale<float>(pt.y());
|
||||
float pz = 0;
|
||||
|
||||
//Calculate space plane
|
||||
float d = -(v1.x() * n.x() + v1.y() * n.y() + v1.z() * n.z());
|
||||
|
||||
float u = -(n.x() * px + n.y() * py + n.z() * pz + d) / n.z();
|
||||
|
||||
//scale up again
|
||||
return scale_(u);
|
||||
}
|
||||
|
||||
// http://paulbourke.net/geometry/pointlineplane/index.html
|
||||
Vec3d*
|
||||
Line_intersection(Vec3d p1, Vec3d p2, Point p3, Point p4) {
|
||||
|
||||
float denom = ((p4.y() - p3.y())*(p2.x() - p1.x())) -
|
||||
((p4.x() - p3.x())*(p2.y() - p1.y()));
|
||||
|
||||
float nume_a = ((p4.x() - p3.x())*(p1.y() - p3.y())) -
|
||||
((p4.y() - p3.y())*(p1.x() - p3.x()));
|
||||
|
||||
float nume_b = ((p2.x() - p1.x())*(p1.y() - p3.y())) -
|
||||
((p2.y() - p1.y())*(p1.x() - p3.x()));
|
||||
|
||||
if(denom == 0.0f)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
float ua = nume_a / denom;
|
||||
float ub = nume_b / denom;
|
||||
|
||||
if(ua >= 0.0f && ua <= 1.0f && ub >= 0.0f && ub <= 1.0f)
|
||||
{
|
||||
// Get the intersection point anc calculate z component
|
||||
Vec3d* ret = new Vec3d();
|
||||
ret->x() = p1.x() + ua*(p2.x() - p1.x());
|
||||
ret->y() = p1.y() + ua*(p2.y() - p1.y());
|
||||
ret->z() = p1.z() - ((sqrt((p1.x()-ret->x())*(p1.x()-ret->x()) + (p1.y()-ret->y())*(p1.y()-ret->y()))
|
||||
/ sqrt((p1.x()-p2.x())*(p1.x()-p2.x()) + (p1.y()-p2.y())*(p1.y()-p2.y())))
|
||||
* (p1.z() - p2.z()));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::Geometry
|
||||
|
@ -529,6 +529,23 @@ Vec<3, T> spheric_to_dir(const Pair &v)
|
||||
return spheric_to_dir<T>(plr, azm);
|
||||
}
|
||||
|
||||
inline float sign(Vec2f p1, Vec2f p2, Vec2f p3)
|
||||
{
|
||||
return (p1.x() - p3.x()) * (p2.y() - p3.y()) - (p2.x() - p3.x()) * (p1.y() - p3.y());
|
||||
}
|
||||
|
||||
bool Point_in_triangle(Vec2f pt, Vec2f v1, Vec2f v2, Vec2f v3);
|
||||
|
||||
//https://graphics.stanford.edu/~mdfisher/Code/Engine/Plane.cpp.html
|
||||
coord_t Project_point_on_plane(Vec3f v1, Vec3f n, Point pt);
|
||||
|
||||
// http://paulbourke.net/geometry/pointlineplane/index.html
|
||||
Vec3d *Line_intersection(Vec3d p1, Vec3d p2, Point p3, Point p4);
|
||||
|
||||
inline float triangle_surface(Point p1, Point p2, Point p3) {
|
||||
return 0.5 * ((p2.x()-p1.x()) * (p3.y()-p1.y()) - (p2.y()-p1.y()) * (p3.x()-p1.x()));
|
||||
}
|
||||
|
||||
} } // namespace Slicer::Geometry
|
||||
|
||||
#endif
|
||||
|
@ -54,6 +54,8 @@ void Layer::make_slices()
|
||||
}
|
||||
// lslices are sorted by topological order from outside to inside from the clipper union used above
|
||||
this->lslices = slices;
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "make_slices layer " << id() << " found " << slices.size() << " slices";
|
||||
}
|
||||
|
||||
this->lslice_indices_sorted_by_print_order = chain_expolygons(this->lslices);
|
||||
@ -1066,7 +1068,7 @@ void Layer::export_region_slices_to_svg(const char *path) const
|
||||
void Layer::export_region_slices_to_svg_debug(const char *name) const
|
||||
{
|
||||
static size_t idx = 0;
|
||||
this->export_region_slices_to_svg(debug_out_path("Layer-slices-%s-%d.svg", name, idx ++).c_str());
|
||||
this->export_region_slices_to_svg(debug_out_path("Layer-%d-slices-%s-%d.svg", id(), name, idx ++).c_str());
|
||||
}
|
||||
|
||||
void Layer::export_region_fill_surfaces_to_svg(const char *path) const
|
||||
@ -1092,7 +1094,57 @@ void Layer::export_region_fill_surfaces_to_svg(const char *path) const
|
||||
void Layer::export_region_fill_surfaces_to_svg_debug(const char *name) const
|
||||
{
|
||||
static size_t idx = 0;
|
||||
this->export_region_fill_surfaces_to_svg(debug_out_path("Layer-fill_surfaces-%s-%d.svg", name, idx ++).c_str());
|
||||
this->export_region_fill_surfaces_to_svg(debug_out_path("Layer-%d-fill_surfaces-%s-%d.svg", id(), name, idx ++).c_str());
|
||||
}
|
||||
|
||||
void Layer::export_lslices_polygons_to_svg(const char *path) const
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const auto *region : m_regions)
|
||||
for (const auto &surface : region->slices())
|
||||
bbox.merge(get_extents(surface.expolygon));
|
||||
|
||||
SVG svg(path, bbox);
|
||||
for (int i = 0; i< lslices.size(); i++)
|
||||
svg.draw(lslices[i], "black", 1.0);
|
||||
svg.Close();
|
||||
}
|
||||
|
||||
// Export to "out/Layer-%name-%idx.svg" with an increasing index with every export.
|
||||
void Layer::export_lslices_polygons_to_svg_debug(const char *name) const
|
||||
{
|
||||
static size_t idx = 0;
|
||||
this->export_lslices_polygons_to_svg(debug_out_path("Layer-%d-lslice_polygons-%s-%d.svg", id(), name, idx ++).c_str());
|
||||
}
|
||||
|
||||
void
|
||||
LayerRegion::remove_nonplanar_slices(SurfaceCollection topNonplanar) {
|
||||
Surfaces layerm_slices_surfaces(m_slices.surfaces);
|
||||
|
||||
//save previously detected nonplanar surfaces
|
||||
SurfaceCollection polyNonplanar;
|
||||
for(Surface s : m_slices.surfaces) {
|
||||
if (s.is_nonplanar()) {
|
||||
polyNonplanar.surfaces.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
// clear internal surfaces
|
||||
m_slices.clear();
|
||||
|
||||
// append internal surfaces again without the found topNonplanar surfaces
|
||||
m_slices.append(
|
||||
diff_ex(
|
||||
union_ex(layerm_slices_surfaces),
|
||||
topNonplanar.surfaces,
|
||||
ApplySafetyOffset::No),
|
||||
stInternal
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
LayerRegion::append_top_nonplanar_slices(SurfaceCollection topNonplanar) {
|
||||
m_slices.append(std::move(topNonplanar));
|
||||
}
|
||||
|
||||
BoundingBox get_extents(const LayerRegion &layer_region)
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "Flow.hpp"
|
||||
#include "SurfaceCollection.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "NonplanarSurface.hpp"
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
@ -133,6 +134,9 @@ public:
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
[[nodiscard]] const ExtrusionEntityCollection& fills() const { return m_fills; }
|
||||
|
||||
// Vector of nonplanar_surfaces which are homed in this layer
|
||||
[[nodiscard]] const NonplanarSurfaces& nonplanar_surfaces() const { return m_nonplanar_surfaces; }
|
||||
|
||||
Flow flow(FlowRole role) const;
|
||||
Flow flow(FlowRole role, double layer_height) const;
|
||||
Flow bridging_flow(FlowRole role, bool force_thick_bridges = false) const;
|
||||
@ -167,6 +171,20 @@ public:
|
||||
// Is there any valid extrusion assigned to this LayerRegion?
|
||||
bool has_extrusions() const { return ! this->perimeters().empty() || ! this->fills().empty(); }
|
||||
|
||||
//append a new nonplanar surface to the list skip if already in list
|
||||
void append_nonplanar_surface(NonplanarSurface& surface);
|
||||
// Projects the paths of a collection regarding the structure of a stl mesh
|
||||
void project_nonplanar_extrusion(ExtrusionEntityCollection* collection);
|
||||
/// Projects nonplanar surfaces downwards regarding the structure of the stl mesh.
|
||||
void project_nonplanar_surfaces();
|
||||
///project a nonplanar path
|
||||
void project_nonplanar_path(ExtrusionPath* path);
|
||||
///sets the z coordinates correctly for all points TODO move to generation of points
|
||||
void correct_z_on_path(ExtrusionPath *path);
|
||||
//
|
||||
void remove_nonplanar_slices(SurfaceCollection topNonplanar);
|
||||
void append_top_nonplanar_slices(SurfaceCollection topNonplanar);
|
||||
|
||||
protected:
|
||||
friend class Layer;
|
||||
friend class PrintObject;
|
||||
@ -225,6 +243,8 @@ private:
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
ExtrusionEntityCollection m_fills;
|
||||
|
||||
NonplanarSurfaces m_nonplanar_surfaces;
|
||||
|
||||
// collection of expolygons representing the bridged areas (thus not
|
||||
// needing support material)
|
||||
// Polygons bridged;
|
||||
@ -379,9 +399,11 @@ public:
|
||||
|
||||
void export_region_slices_to_svg(const char *path) const;
|
||||
void export_region_fill_surfaces_to_svg(const char *path) const;
|
||||
void export_lslices_polygons_to_svg(const char *name) const;
|
||||
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
|
||||
void export_region_slices_to_svg_debug(const char *name) const;
|
||||
void export_region_fill_surfaces_to_svg_debug(const char *name) const;
|
||||
void export_lslices_polygons_to_svg_debug(const char *name) const;
|
||||
|
||||
// Is there any valid extrusion assigned to this LayerRegion?
|
||||
virtual bool has_extrusions() const { for (auto layerm : m_regions) if (layerm->has_extrusions()) return true; return false; }
|
||||
|
@ -463,10 +463,16 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTop, shells,
|
||||
RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps),
|
||||
sparse, expansion_params_into_sparse_infill, closing_radius);
|
||||
Surfaces nonplanarTops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTopNonplanar, shells,
|
||||
RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps),
|
||||
sparse, expansion_params_into_sparse_infill, closing_radius);
|
||||
Surfaces nonplanarInternals = expand_merge_surfaces(m_fill_surfaces.surfaces, stInternalSolidNonplanar, shells,
|
||||
RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps),
|
||||
sparse, expansion_params_into_sparse_infill, closing_radius);
|
||||
|
||||
// m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternal, stInternalSolid });
|
||||
m_fill_surfaces.clear();
|
||||
reserve_more(m_fill_surfaces.surfaces, shells.size() + sparse.size() + bridges.size() + bottoms.size() + tops.size());
|
||||
reserve_more(m_fill_surfaces.surfaces, shells.size() + sparse.size() + bridges.size() + bottoms.size() + tops.size() + nonplanarTops.size() + nonplanarInternals.size());
|
||||
{
|
||||
Surface solid_templ(stInternalSolid, {});
|
||||
solid_templ.thickness = layer_thickness;
|
||||
@ -480,6 +486,8 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
m_fill_surfaces.append(std::move(bridges.surfaces));
|
||||
m_fill_surfaces.append(std::move(bottoms));
|
||||
m_fill_surfaces.append(std::move(tops));
|
||||
m_fill_surfaces.append(std::move(nonplanarTops));
|
||||
m_fill_surfaces.append(std::move(nonplanarInternals));
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final");
|
||||
@ -876,7 +884,7 @@ void LayerRegion::export_region_slices_to_svg_debug(const char *name) const
|
||||
{
|
||||
static std::map<std::string, size_t> idx_map;
|
||||
size_t &idx = idx_map[name];
|
||||
this->export_region_slices_to_svg(debug_out_path("LayerRegion-slices-%s-%d.svg", name, idx ++).c_str());
|
||||
this->export_region_slices_to_svg(debug_out_path("LayerRegion-%d-slices-%s-%d.svg", layer()->id(), name, idx ++).c_str());
|
||||
}
|
||||
|
||||
void LayerRegion::export_region_fill_surfaces_to_svg(const char *path) const
|
||||
@ -903,7 +911,246 @@ void LayerRegion::export_region_fill_surfaces_to_svg_debug(const char *name) con
|
||||
{
|
||||
static std::map<std::string, size_t> idx_map;
|
||||
size_t &idx = idx_map[name];
|
||||
this->export_region_fill_surfaces_to_svg(debug_out_path("LayerRegion-fill_surfaces-%s-%d.svg", name, idx ++).c_str());
|
||||
this->export_region_fill_surfaces_to_svg(debug_out_path("LayerRegion-%d-fill_surfaces-%s-%d.svg", layer()->id(), name, idx ++).c_str());
|
||||
}
|
||||
|
||||
void
|
||||
LayerRegion::append_nonplanar_surface(NonplanarSurface& surface)
|
||||
{
|
||||
for(auto & s : m_nonplanar_surfaces){
|
||||
if (s == surface){
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_nonplanar_surfaces.push_back(surface);
|
||||
}
|
||||
|
||||
void
|
||||
LayerRegion::project_nonplanar_extrusion(ExtrusionEntityCollection *collection)
|
||||
{
|
||||
for (auto& entity : collection->entities) {
|
||||
if (ExtrusionLoop* loop = dynamic_cast<ExtrusionLoop*>(entity)) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Projecting extrusion loop with " << loop->paths.size() << " paths";
|
||||
|
||||
for(auto& path : loop->paths) {
|
||||
project_nonplanar_path(&path);
|
||||
correct_z_on_path(&path);
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_to_svg(debug_out_path("Layer-%d_project_extrusion_loop-%u-after.svg", layer()->id(), iRun ++).c_str(),
|
||||
loop->as_polyline(), get_extents(this->slices().surfaces), scale_(0.1f));
|
||||
}
|
||||
#endif
|
||||
} else if (ExtrusionMultiPath* multipath = dynamic_cast<ExtrusionMultiPath*>(entity)) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Projecting extrusion multipath with " << multipath->paths.size() << " paths";
|
||||
|
||||
for (auto& path : multipath->paths) {
|
||||
project_nonplanar_path(&path);
|
||||
correct_z_on_path(&path);
|
||||
}
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_to_svg(debug_out_path("Layer-%d_project_extrusion_multipath-%u-after.svg", layer()->id(), iRun ++).c_str(),
|
||||
multipath->as_polyline(), get_extents(this->slices().surfaces), scale_(0.1f));
|
||||
}
|
||||
#endif
|
||||
} else if (ExtrusionPath* path = dynamic_cast<ExtrusionPath*>(entity)) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Projecting 1 extrusion path";
|
||||
|
||||
project_nonplanar_path(path);
|
||||
correct_z_on_path(path);
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_to_svg(debug_out_path("Layer-%d_project_extrusion_path-%u-after.svg", layer()->id(), iRun ++).c_str(),
|
||||
path->as_polyline(), get_extents(this->slices().surfaces), scale_(0.1f));
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Unknown entity class " << typeid(entity).name();
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LayerRegion::project_nonplanar_surfaces()
|
||||
{
|
||||
// skip if there are no nonplanar_surfaces on this LayerRegion
|
||||
if (m_nonplanar_surfaces.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Projecting " << m_nonplanar_surfaces.size() << " nonplanar surfaces for region " <<
|
||||
region().print_region_id() << " and layer " << layer()->id() << " (z = " << layer()->print_z << ")";
|
||||
|
||||
// for all perimeters do path projection
|
||||
BOOST_LOG_TRIVIAL(trace) << "Projecting " << m_perimeters.size() << " nonplanar perimeters";
|
||||
for (auto& col : m_perimeters.entities) {
|
||||
ExtrusionEntityCollection* collection = dynamic_cast<ExtrusionEntityCollection*>(col);
|
||||
this->project_nonplanar_extrusion(collection);
|
||||
}
|
||||
|
||||
// and all fill paths do path projection
|
||||
BOOST_LOG_TRIVIAL(trace) << "Projecting " << m_fills.size() << " nonplanar fills";
|
||||
for (auto& col : m_fills.entities) {
|
||||
ExtrusionEntityCollection* collection = dynamic_cast<ExtrusionEntityCollection*>(col);
|
||||
this->project_nonplanar_extrusion(collection);
|
||||
}
|
||||
}
|
||||
|
||||
//Sorting functions for soring of paths
|
||||
bool
|
||||
greaterX(const Vec3d &a, const Vec3d &b)
|
||||
{
|
||||
return (a.x() < b.x());
|
||||
}
|
||||
|
||||
bool
|
||||
smallerX(const Vec3d &a, const Vec3d &b)
|
||||
{
|
||||
return (a.x() > b.x());
|
||||
}
|
||||
|
||||
bool
|
||||
greaterY(const Vec3d &a, const Vec3d &b)
|
||||
{
|
||||
return (a.y() < b.y());
|
||||
}
|
||||
|
||||
bool
|
||||
smallerY(const Vec3d &a, const Vec3d &b)
|
||||
{
|
||||
return (a.y() > b.y());
|
||||
}
|
||||
|
||||
void
|
||||
LayerRegion::project_nonplanar_path(ExtrusionPath *path)
|
||||
{
|
||||
//First check all points and project them regarding the triangle mesh
|
||||
for (Point& point : path->polyline.points) {
|
||||
for (auto& surface : m_nonplanar_surfaces) {
|
||||
float distance_to_top = surface.stats.max.z - this->layer()->print_z;
|
||||
for(auto& facet : surface.mesh) {
|
||||
// skip if point is outside of the bounding box of the triangle
|
||||
if (unscale(point).x() < std::min({facet.second.vertex[0].x, facet.second.vertex[1].x, facet.second.vertex[2].x}) ||
|
||||
unscale(point).x() > std::max({facet.second.vertex[0].x, facet.second.vertex[1].x, facet.second.vertex[2].x}) ||
|
||||
unscale(point).y() < std::min({facet.second.vertex[0].y, facet.second.vertex[1].y, facet.second.vertex[2].y}) ||
|
||||
unscale(point).y() > std::max({facet.second.vertex[0].y, facet.second.vertex[1].y, facet.second.vertex[2].y}))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//check if point is inside of Triangle
|
||||
if (Slic3r::Geometry::Point_in_triangle(
|
||||
Vec2f(unscale(point).x(), unscale(point).y()),
|
||||
Vec2f(facet.second.vertex[0].x, facet.second.vertex[0].y),
|
||||
Vec2f(facet.second.vertex[1].x, facet.second.vertex[1].y),
|
||||
Vec2f(facet.second.vertex[2].x, facet.second.vertex[2].y))
|
||||
&& (facet.second.normal.z != 0))
|
||||
{
|
||||
coord_t z = Slic3r::Geometry::Project_point_on_plane(Vec3f(facet.second.vertex[0].x,facet.second.vertex[0].y,facet.second.vertex[0].z),
|
||||
Vec3f(facet.second.normal.x,facet.second.normal.y,facet.second.normal.z),
|
||||
point);
|
||||
|
||||
//Shift down when on lower layer
|
||||
point.nonplanar_z = z - scale_(distance_to_top);
|
||||
//break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then check all line intersections, cut line on intersection and project new point
|
||||
std::vector<Vec3crd>::size_type size = path->polyline.points.size();
|
||||
for (std::vector<Vec3crd>::size_type i = 0; i < size-1; ++i)
|
||||
{
|
||||
Pointf3s intersections;
|
||||
// check against every facet if lines intersect
|
||||
for (auto& surface : m_nonplanar_surfaces) {
|
||||
float distance_to_top = surface.stats.max.z - this->layer()->print_z;
|
||||
for(auto& facet : surface.mesh) {
|
||||
for(int j= 0; j < 3; j++) {
|
||||
// TODO precheck for faster computation
|
||||
Vec3d p1 = Vec3d(scale_(facet.second.vertex[j].x), scale_(facet.second.vertex[j].y), scale_(facet.second.vertex[j].z));
|
||||
Vec3d p2 = Vec3d(scale_(facet.second.vertex[(j+1) % 3].x), scale_(facet.second.vertex[(j+1) % 3].y), scale_(facet.second.vertex[(j+1) % 3].z));
|
||||
Vec3d* p = Slic3r::Geometry::Line_intersection(p1, p2, path->polyline.points[i], path->polyline.points[i+1]);
|
||||
|
||||
if (p) {
|
||||
// add distance to top for every added point
|
||||
p->z() = p->z() - scale_(distance_to_top);
|
||||
intersections.push_back(*p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop if no intersections are found
|
||||
if (intersections.size() == 0) continue;
|
||||
|
||||
// sort found intersectons if there are more than 1
|
||||
if ( intersections.size() > 1 ){
|
||||
if (abs(path->polyline.points[i+1].x() - path->polyline.points[i].x()) >= abs(path->polyline.points[i+1].y() - path->polyline.points[i].y()) ) {
|
||||
// sort by X
|
||||
if(path->polyline.points[i].x() < path->polyline.points[i+1].x()) {
|
||||
std::sort(intersections.begin(), intersections.end(), smallerX);
|
||||
}else {
|
||||
std::sort(intersections.begin(), intersections.end(), greaterX);
|
||||
}
|
||||
} else {
|
||||
// sort by Y
|
||||
if(path->polyline.points[i].y() < path->polyline.points[i+1].y()) {
|
||||
std::sort(intersections.begin(), intersections.end(), smallerY);
|
||||
}else {
|
||||
std::sort(intersections.begin(), intersections.end(), greaterY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove duplicates
|
||||
Pointf3s::iterator point = intersections.begin();
|
||||
while (point != intersections.end()-1) {
|
||||
bool delete_point = false;
|
||||
Pointf3s::iterator point2 = point;
|
||||
++point2;
|
||||
//compare with next point if they are the same, delete current point
|
||||
if ((*point).x() == (*point2).x() && (*point).y() == (*point2).y()) {
|
||||
//remove duplicate point
|
||||
delete_point = true;
|
||||
point = intersections.erase(point);
|
||||
}
|
||||
//continue loop when no point is removed. Otherwise the new point is set while deleting the old one.
|
||||
if (!delete_point) {
|
||||
++point;
|
||||
}
|
||||
}
|
||||
|
||||
//insert new points into array
|
||||
for (Vec3d p : intersections)
|
||||
{
|
||||
Point *pt = new Point(p.x(), p.y());
|
||||
pt->nonplanar_z = p.z();
|
||||
path->polyline.points.insert(path->polyline.points.begin()+i+1, *pt);
|
||||
}
|
||||
|
||||
//modifiy array boundary
|
||||
i = i + intersections.size();
|
||||
size = size + intersections.size();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LayerRegion::correct_z_on_path(ExtrusionPath *path)
|
||||
{
|
||||
for (Point& point : path->polyline.points) {
|
||||
if(point.nonplanar_z == -1) {
|
||||
point.nonplanar_z = scale_(this->layer()->print_z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -103,8 +103,28 @@ bool MultiPoint::remove_duplicate_points()
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
MultiPoint::needs_zmove(const Points &pts)
|
||||
{
|
||||
coord_t z = -1;
|
||||
//check if any point in travel is below the layer z
|
||||
for (Point p : pts)
|
||||
{
|
||||
if(z == -1) {
|
||||
z = p.nonplanar_z;
|
||||
}
|
||||
else if ((p.nonplanar_z != -1) && (p.nonplanar_z != z))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Points MultiPoint::douglas_peucker(const Points &pts, const double tolerance)
|
||||
{
|
||||
if(MultiPoint::needs_zmove(pts))
|
||||
return pts;
|
||||
|
||||
Points result_pts;
|
||||
auto tolerance_sq = int64_t(sqr(tolerance));
|
||||
if (! pts.empty()) {
|
||||
|
@ -81,6 +81,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
static bool needs_zmove(const Points &pts);
|
||||
static Points douglas_peucker(const Points &points, const double tolerance);
|
||||
static Points visivalingam(const Points& pts, const double& tolerance);
|
||||
|
||||
|
72
src/libslic3r/NonplanarFacet.cpp
Normal file
72
src/libslic3r/NonplanarFacet.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
#include "NonplanarFacet.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void
|
||||
NonplanarFacet::calculate_stats() {
|
||||
//calculate min and max values
|
||||
this->stats.min.x = this->vertex[0].x;
|
||||
this->stats.min.y = this->vertex[0].y;
|
||||
this->stats.min.z = this->vertex[0].z;
|
||||
this->stats.max.x = this->vertex[0].x;
|
||||
this->stats.max.y = this->vertex[0].y;
|
||||
this->stats.max.z = this->vertex[0].z;
|
||||
for(int j = 1; j < 3; j++) {
|
||||
this->stats.min.x = std::min(this->stats.min.x, this->vertex[j].x);
|
||||
this->stats.min.y = std::min(this->stats.min.y, this->vertex[j].y);
|
||||
this->stats.min.z = std::min(this->stats.min.z, this->vertex[j].z);
|
||||
this->stats.max.x = std::max(this->stats.max.x, this->vertex[j].x);
|
||||
this->stats.max.y = std::max(this->stats.max.y, this->vertex[j].y);
|
||||
this->stats.max.z = std::max(this->stats.max.z, this->vertex[j].z);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
NonplanarFacet::translate(float x, float y, float z)
|
||||
{
|
||||
//translate facet
|
||||
for(int j = 0; j < 3; j++) {
|
||||
this->vertex[j].x += x;
|
||||
this->vertex[j].y += y;
|
||||
this->vertex[j].z += z;
|
||||
}
|
||||
|
||||
//translate min and max values
|
||||
this->stats.min.x += x;
|
||||
this->stats.min.y += y;
|
||||
this->stats.min.z += z;
|
||||
this->stats.max.x += x;
|
||||
this->stats.max.y += y;
|
||||
this->stats.max.z += z;
|
||||
}
|
||||
|
||||
void
|
||||
NonplanarFacet::scale(float versor[3])
|
||||
{
|
||||
//scale facet
|
||||
for(int j = 0; j < 3; j++) {
|
||||
this->vertex[j].x *= versor[0];
|
||||
this->vertex[j].y *= versor[1];
|
||||
this->vertex[j].z *= versor[2];
|
||||
}
|
||||
|
||||
//scale min and max values
|
||||
this->stats.min.x *= versor[0];
|
||||
this->stats.min.y *= versor[1];
|
||||
this->stats.min.z *= versor[2];
|
||||
this->stats.max.x *= versor[0];
|
||||
this->stats.max.y *= versor[1];
|
||||
this->stats.max.z *= versor[2];
|
||||
}
|
||||
|
||||
float
|
||||
NonplanarFacet::calculate_surface_area()
|
||||
{
|
||||
return Slic3r::Geometry::triangle_surface(
|
||||
Point(this->vertex[0].x, this->vertex[0].y),
|
||||
Point(this->vertex[1].x, this->vertex[1].y),
|
||||
Point(this->vertex[2].x, this->vertex[2].y)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
39
src/libslic3r/NonplanarFacet.hpp
Normal file
39
src/libslic3r/NonplanarFacet.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
#ifndef slic3r_NonplanarFacet_hpp_
|
||||
#define slic3r_NonplanarFacet_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "Geometry.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
} facet_vertex;
|
||||
|
||||
typedef struct {
|
||||
facet_vertex max;
|
||||
facet_vertex min;
|
||||
} facet_stats;
|
||||
|
||||
class NonplanarFacet
|
||||
{
|
||||
public:
|
||||
facet_vertex vertex[3];
|
||||
facet_vertex normal;
|
||||
int neighbor[3];
|
||||
facet_stats stats;
|
||||
bool marked = false;
|
||||
|
||||
NonplanarFacet() {};
|
||||
~NonplanarFacet() {};
|
||||
void calculate_stats();
|
||||
void translate(float x, float y, float z);
|
||||
void scale(float versor[3]);
|
||||
float calculate_surface_area();
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
240
src/libslic3r/NonplanarSurface.cpp
Normal file
240
src/libslic3r/NonplanarSurface.cpp
Normal file
@ -0,0 +1,240 @@
|
||||
#include "NonplanarSurface.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
NonplanarSurface::NonplanarSurface(std::map<int, NonplanarFacet> &_mesh)
|
||||
{
|
||||
this->mesh = _mesh;
|
||||
this->calculate_stats();
|
||||
}
|
||||
|
||||
bool
|
||||
NonplanarSurface::operator==(const NonplanarSurface& other) const {
|
||||
return (stats.min.x == other.stats.min.x &&
|
||||
stats.min.y == other.stats.min.y &&
|
||||
stats.min.z == other.stats.min.z &&
|
||||
stats.max.x == other.stats.max.x &&
|
||||
stats.max.y == other.stats.max.y &&
|
||||
stats.max.z == other.stats.max.z);
|
||||
}
|
||||
|
||||
void
|
||||
NonplanarSurface::calculate_stats()
|
||||
{
|
||||
//calculate min and max values
|
||||
this->stats.min.x = 10000000;
|
||||
this->stats.min.y = 10000000;
|
||||
this->stats.min.z = 10000000;
|
||||
this->stats.max.x = -10000000;
|
||||
this->stats.max.y = -10000000;
|
||||
this->stats.max.z = -10000000;
|
||||
for(auto& facet : this->mesh) {
|
||||
this->stats.min.x = std::min(this->stats.min.x, facet.second.stats.min.x);
|
||||
this->stats.min.y = std::min(this->stats.min.y, facet.second.stats.min.y);
|
||||
this->stats.min.z = std::min(this->stats.min.z, facet.second.stats.min.z);
|
||||
this->stats.max.x = std::max(this->stats.max.x, facet.second.stats.max.x);
|
||||
this->stats.max.y = std::max(this->stats.max.y, facet.second.stats.max.y);
|
||||
this->stats.max.z = std::max(this->stats.max.z, facet.second.stats.max.z);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
NonplanarSurface::translate(float x, float y, float z)
|
||||
{
|
||||
//translate all facets
|
||||
for(auto& facet : this->mesh) {
|
||||
facet.second.translate(x, y, z);
|
||||
}
|
||||
|
||||
//translate min and max values
|
||||
this->stats.min.x += x;
|
||||
this->stats.min.y += y;
|
||||
this->stats.min.z += z;
|
||||
this->stats.max.x += x;
|
||||
this->stats.max.y += y;
|
||||
this->stats.max.z += z;
|
||||
}
|
||||
|
||||
void
|
||||
NonplanarSurface::scale(float factor)
|
||||
{
|
||||
float versor[3];
|
||||
versor[0] = factor;
|
||||
versor[1] = factor;
|
||||
versor[2] = factor;
|
||||
this->scale(versor);
|
||||
}
|
||||
|
||||
void
|
||||
NonplanarSurface::scale(float versor[3])
|
||||
{
|
||||
//scale all facets
|
||||
for(auto& facet : this->mesh) {
|
||||
facet.second.scale(versor);
|
||||
}
|
||||
|
||||
//scale min and max values
|
||||
this->stats.min.x *= versor[0];
|
||||
this->stats.min.y *= versor[1];
|
||||
this->stats.min.z *= versor[2];
|
||||
this->stats.max.x *= versor[0];
|
||||
this->stats.max.y *= versor[1];
|
||||
this->stats.max.z *= versor[2];
|
||||
}
|
||||
|
||||
void
|
||||
NonplanarSurface::rotate_z(float angle) {
|
||||
double radian_angle = (angle / 180.0) * 3.1415927;
|
||||
double c = cos(radian_angle);
|
||||
double s = sin(radian_angle);
|
||||
|
||||
for(auto& facet : this->mesh) {
|
||||
for(int j = 0; j < 3; j++) {
|
||||
double xold = facet.second.vertex[j].x;
|
||||
double yold = facet.second.vertex[j].y;
|
||||
facet.second.vertex[j].x = c * xold - s * yold;
|
||||
facet.second.vertex[j].y = s * xold + c * yold;
|
||||
}
|
||||
facet.second.calculate_stats();
|
||||
}
|
||||
|
||||
this->calculate_stats();
|
||||
}
|
||||
|
||||
void
|
||||
NonplanarSurface::debug_output()
|
||||
{
|
||||
std::cout << "Facets(" << this->mesh.size() << "): (min:X:" << this->stats.min.x << " Y:" << this->stats.min.y << " Z:" << this->stats.min.z <<
|
||||
" max:X:" << this->stats.max.x << " Y:" << this->stats.max.y << " Z:" << this->stats.max.z << ")" <<
|
||||
"Height " << this->stats.max.z - this->stats.min.z << std::endl;
|
||||
for(auto& facet : this->mesh) {
|
||||
std::cout << "triangle: (" << facet.first << ")(" << facet.second.marked << ") ";
|
||||
std::cout << " (" << (180*std::acos(facet.second.normal.z))/3.14159265 << "°)";
|
||||
|
||||
std::cout << " | V0:";
|
||||
std::cout << " X:"<< facet.second.vertex[0].x;
|
||||
std::cout << " Y:"<< facet.second.vertex[0].y;
|
||||
std::cout << " Z:"<< facet.second.vertex[0].z;
|
||||
|
||||
std::cout << " | V1:";
|
||||
std::cout << " X:"<< facet.second.vertex[1].x;
|
||||
std::cout << " Y:"<< facet.second.vertex[1].y;
|
||||
std::cout << " Z:"<< facet.second.vertex[1].z;
|
||||
|
||||
std::cout << " | V2:";
|
||||
std::cout << " X:"<< facet.second.vertex[2].x;
|
||||
std::cout << " Y:"<< facet.second.vertex[2].y;
|
||||
std::cout << " Z:"<< facet.second.vertex[2].z;
|
||||
|
||||
std::cout << " | Normal:";
|
||||
std::cout << " X:"<< facet.second.normal.x;
|
||||
std::cout << " Y:"<< facet.second.normal.y;
|
||||
std::cout << " Z:"<< facet.second.normal.z;
|
||||
|
||||
//TODO check if neighbors exist
|
||||
// stl_neighbors* neighbors = mesh.stl.neighbors_start + facet.first;
|
||||
std::cout << " | Neighbors:";
|
||||
std::cout << " 0:"<< facet.second.neighbor[0];
|
||||
std::cout << " 1:"<< facet.second.neighbor[1];
|
||||
std::cout << " 2:"<< facet.second.neighbor[2];
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
NonplanarSurfaces
|
||||
NonplanarSurface::group_surfaces()
|
||||
{
|
||||
std::pair<int, NonplanarFacet> begin = *this->mesh.begin();
|
||||
this->mark_neighbor_surfaces(begin.first);
|
||||
NonplanarSurface newSurface;
|
||||
for (std::map<int, NonplanarFacet>::iterator it=this->mesh.begin(); it!=this->mesh.end();) {
|
||||
if((*it).second.marked == false) {
|
||||
newSurface.mesh[(*it).first] = (*it).second;
|
||||
it = this->mesh.erase(it);
|
||||
}
|
||||
else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
this->calculate_stats();
|
||||
if (newSurface.mesh.size() == 0) {
|
||||
//return only this
|
||||
NonplanarSurfaces nonplanar_surfaces;
|
||||
nonplanar_surfaces.push_back(*this);
|
||||
return nonplanar_surfaces;
|
||||
}
|
||||
else {
|
||||
//return union of this and recursion
|
||||
NonplanarSurfaces nonplanar_surfaces = newSurface.group_surfaces();
|
||||
nonplanar_surfaces.push_back(*this);
|
||||
return nonplanar_surfaces;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
NonplanarSurface::mark_neighbor_surfaces(int id)
|
||||
{
|
||||
//if already marked return
|
||||
if(this->mesh.find(id) == this->mesh.end() || this->mesh[id].marked == true) return;
|
||||
//mark this facet
|
||||
this->mesh[id].marked = true;
|
||||
//mark all neighbors
|
||||
for(int j = 0; j < 3; j++) {
|
||||
this->mark_neighbor_surfaces(this->mesh[id].neighbor[j]);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
NonplanarSurface::check_max_printing_height(float height)
|
||||
{
|
||||
if ((this->stats.max.z - this->stats.min.z) > height ) {
|
||||
std::cout << "Surface removed: printheight too heigh (" << (this->stats.max.z - this->stats.min.z) << " mm)" << '\n';
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
NonplanarSurface::check_surface_area()
|
||||
{
|
||||
//calculate surface area of nonplanar surface.
|
||||
float area = 0.0f;
|
||||
for(auto& facet : this->mesh) {
|
||||
area += facet.second.calculate_surface_area();
|
||||
}
|
||||
if (area < 20.0f) {
|
||||
std::cout << "Surface removed: area too small (" << area << " mm²)" << '\n';
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
NonplanarSurface::check_printable_surfaces(float max_angle)
|
||||
{
|
||||
//TODO do something
|
||||
}
|
||||
|
||||
/* this will return scaled ExPolygons */
|
||||
ExPolygons
|
||||
NonplanarSurface::horizontal_projection() const
|
||||
{
|
||||
Polygons pp;
|
||||
pp.reserve(this->mesh.size());
|
||||
for(auto& facet : this->mesh) {
|
||||
Polygon p;
|
||||
p.points.resize(3);
|
||||
p.points[0] = Point(scale_(facet.second.vertex[0].x), scale_(facet.second.vertex[0].y));
|
||||
p.points[1] = Point(scale_(facet.second.vertex[1].x), scale_(facet.second.vertex[1].y));
|
||||
p.points[2] = Point(scale_(facet.second.vertex[2].x), scale_(facet.second.vertex[2].y));
|
||||
p.make_counter_clockwise(); // do this after scaling, as winding order might change while doing that
|
||||
pp.push_back(p);
|
||||
}
|
||||
|
||||
// the offset factor was tuned using groovemount.stl
|
||||
return union_ex(offset(pp, scale_(0.01)));
|
||||
}
|
||||
|
||||
}
|
54
src/libslic3r/NonplanarSurface.hpp
Normal file
54
src/libslic3r/NonplanarSurface.hpp
Normal file
@ -0,0 +1,54 @@
|
||||
#ifndef slic3r_NonplanarSurface_hpp_
|
||||
#define slic3r_NonplanarSurface_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "NonplanarFacet.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
} mesh_vertex;
|
||||
|
||||
typedef struct {
|
||||
mesh_vertex max;
|
||||
mesh_vertex min;
|
||||
} mesh_stats;
|
||||
|
||||
class NonplanarSurface;
|
||||
|
||||
typedef std::vector<NonplanarSurface> NonplanarSurfaces;
|
||||
|
||||
class NonplanarSurface
|
||||
{
|
||||
public:
|
||||
std::map<int, NonplanarFacet> mesh;
|
||||
mesh_stats stats;
|
||||
NonplanarSurface() {};
|
||||
~NonplanarSurface() {};
|
||||
NonplanarSurface(std::map<int, NonplanarFacet> &_mesh);
|
||||
bool operator==(const NonplanarSurface& other) const;
|
||||
void calculate_stats();
|
||||
void translate(float x, float y, float z);
|
||||
void scale(float factor);
|
||||
void scale(float versor[3]);
|
||||
void rotate_z(float angle);
|
||||
void debug_output();
|
||||
NonplanarSurfaces group_surfaces();
|
||||
void mark_neighbor_surfaces(int id);
|
||||
bool check_max_printing_height(float height);
|
||||
void check_printable_surfaces(float max_angle);
|
||||
bool check_surface_area();
|
||||
ExPolygons horizontal_projection() const;
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
@ -159,11 +159,14 @@ class Point : public Vec2crd
|
||||
public:
|
||||
using coord_type = coord_t;
|
||||
|
||||
coord_t nonplanar_z { -1 };
|
||||
|
||||
Point() : Vec2crd(0, 0) {}
|
||||
Point(int32_t x, int32_t y) : Vec2crd(coord_t(x), coord_t(y)) {}
|
||||
Point(int64_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {}
|
||||
Point(double x, double y) : Vec2crd(coord_t(std::round(x)), coord_t(std::round(y))) {}
|
||||
Point(const Point &rhs) { *this = rhs; }
|
||||
Point(Vec3crd &p) : Vec2crd(p.x(), p.y()) {}
|
||||
explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(std::round(rhs.x())), coord_t(std::round(rhs.y()))) {}
|
||||
// This constructor allows you to construct Point from Eigen expressions
|
||||
template<typename OtherDerived>
|
||||
|
@ -402,7 +402,7 @@ bool has_duplicate_points(const Polygons &polys)
|
||||
{
|
||||
#if 1
|
||||
// Check globally.
|
||||
#if 0
|
||||
#if 1
|
||||
// Detect duplicates by sorting with quicksort. It is quite fast, but ankerl::unordered_dense is around 1/4 faster.
|
||||
Points allpts;
|
||||
allpts.reserve(count_points(polys));
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
|
||||
@ -332,4 +333,29 @@ Lines3 Polyline3::lines() const
|
||||
return lines;
|
||||
}
|
||||
|
||||
bool export_to_svg(const char *path, const Polyline &polyline, BoundingBox bbox, const float stroke_width)
|
||||
{
|
||||
SVG svg(path, bbox);
|
||||
svg.draw(polyline, "black", stroke_width);
|
||||
svg.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool export_to_svg(const char *path, const Polylines &polylines, BoundingBox bbox, const float stroke_width)
|
||||
{
|
||||
SVG svg(path, bbox);
|
||||
svg.draw(polylines, "black", stroke_width);
|
||||
svg.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool export_to_svg(const char *path, const ThickPolylines &polylines, BoundingBox bbox, const float stroke_width)
|
||||
{
|
||||
SVG svg(path, bbox);
|
||||
svg.draw(polylines, "black", stroke_width);
|
||||
svg.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -234,6 +234,10 @@ public:
|
||||
|
||||
typedef std::vector<Polyline3> Polylines3;
|
||||
|
||||
extern bool export_to_svg(const char *path, const Polyline &polyline, BoundingBox bbox, const float stroke_width = 1.f);
|
||||
extern bool export_to_svg(const char *path, const Polylines &polylines, BoundingBox bbox, const float stroke_width = 1.f);
|
||||
extern bool export_to_svg(const char *path, const ThickPolylines &polylines, BoundingBox bbox, const float stroke_width = 1.f);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -432,6 +432,7 @@ static std::vector<std::string> s_Preset_print_options {
|
||||
"ironing", "ironing_type", "ironing_flowrate", "ironing_speed", "ironing_spacing",
|
||||
"max_print_speed", "max_volumetric_speed", "avoid_crossing_perimeters_max_detour",
|
||||
"fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_dist",
|
||||
"use_nonplanar_layers", "nonplanar_layers_angle", "nonplanar_layers_height",
|
||||
"max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative",
|
||||
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed",
|
||||
"enable_dynamic_overhang_speeds", "overhang_speed_0", "overhang_speed_1", "overhang_speed_2", "overhang_speed_3",
|
||||
|
@ -17,6 +17,8 @@
|
||||
#include "GCode/ThumbnailData.hpp"
|
||||
#include "GCode/GCodeProcessor.hpp"
|
||||
#include "MultiMaterialSegmentation.hpp"
|
||||
#include "NonplanarSurface.hpp"
|
||||
#include "NonplanarFacet.hpp"
|
||||
|
||||
#include "libslic3r.h"
|
||||
|
||||
@ -67,7 +69,9 @@ enum PrintStep : unsigned int {
|
||||
|
||||
enum PrintObjectStep : unsigned int {
|
||||
posSlice, posPerimeters, posPrepareInfill,
|
||||
posInfill, posIroning, posSupportSpotsSearch, posSupportMaterial, posEstimateCurledExtrusions, posCount,
|
||||
posInfill, posIroning, posSupportSpotsSearch,
|
||||
posSupportMaterial, posNonplanarProjection, posEstimateCurledExtrusions,
|
||||
posCount,
|
||||
};
|
||||
|
||||
// A PrintRegion object represents a group of volumes to print
|
||||
@ -264,6 +268,8 @@ public:
|
||||
coord_t height() const { return m_size.z(); }
|
||||
// Centering offset of the sliced mesh from the scaled and rotated mesh of the model.
|
||||
const Point& center_offset() const { return m_center_offset; }
|
||||
//
|
||||
NonplanarSurfaces nonplanar_surfaces() { return m_nonplanar_surfaces; }
|
||||
|
||||
bool has_brim() const {
|
||||
return this->config().brim_type != btNoBrim
|
||||
@ -378,8 +384,16 @@ private:
|
||||
void estimate_curled_extrusions();
|
||||
|
||||
void slice_volumes();
|
||||
void make_slices();
|
||||
void lslices_were_updated();
|
||||
// Has any support (not counting the raft).
|
||||
void detect_surfaces_type();
|
||||
void merge_nonplanar_surfaces();
|
||||
void debug_svg_print();
|
||||
bool check_nonplanar_collisions(NonplanarSurface &surface);
|
||||
void project_nonplanar_surfaces();
|
||||
void find_nonplanar_surfaces();
|
||||
void detect_nonplanar_surfaces();
|
||||
void process_external_surfaces();
|
||||
void discover_vertical_shells();
|
||||
void bridge_over_infill();
|
||||
@ -414,6 +428,8 @@ private:
|
||||
// so that next call to make_perimeters() performs a union() before computing loops
|
||||
bool m_typed_slices = false;
|
||||
|
||||
NonplanarSurfaces m_nonplanar_surfaces;
|
||||
|
||||
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> m_adaptive_fill_octrees;
|
||||
FillLightning::GeneratorPtr m_lightning_generator;
|
||||
};
|
||||
|
@ -1369,6 +1369,33 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0.8));
|
||||
|
||||
|
||||
def = this->add("use_nonplanar_layers", coBool);
|
||||
def->label = L("Use nonplanar layers");
|
||||
def->category = L("Nonplanar layers");
|
||||
def->tooltip = L("Generate nonplanar layers on top of the object");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("nonplanar_layers_angle", coFloat);
|
||||
def->label = L("Maximum nonplanar angle");
|
||||
def->category = L("Nonplanar layers");
|
||||
def->tooltip = L("The maximum angle the printer can print without collisions.");
|
||||
def->sidetext = L("°");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(10.0));
|
||||
|
||||
def = this->add("nonplanar_layers_height", coFloat);
|
||||
def->label = L("Maximum nonplanar height");
|
||||
def->category = L("Nonplanar layers");
|
||||
def->tooltip = L("The maximum height the printer can print without collisions.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(10.0));
|
||||
|
||||
|
||||
def = this->add("gap_fill_enabled", coBool);
|
||||
def->label = L("Fill gaps");
|
||||
def->category = L("Layers and Perimeters");
|
||||
|
@ -562,6 +562,10 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionBool, thick_bridges))
|
||||
((ConfigOptionFloat, xy_size_compensation))
|
||||
((ConfigOptionBool, wipe_into_objects))
|
||||
|
||||
((ConfigOptionBool, use_nonplanar_layers))
|
||||
((ConfigOptionFloat, nonplanar_layers_angle))
|
||||
((ConfigOptionFloat, nonplanar_layers_height))
|
||||
)
|
||||
|
||||
PRINT_CONFIG_CLASS_DEFINE(
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "TriangleSelectorWrapper.hpp"
|
||||
#include "format.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "SVG.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
@ -53,6 +54,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <vector>
|
||||
@ -179,6 +181,8 @@ void PrintObject::make_perimeters()
|
||||
m_typed_slices = false;
|
||||
}
|
||||
|
||||
this->detect_nonplanar_surfaces();
|
||||
|
||||
// compare each layer to the one below, and mark those slices needing
|
||||
// one additional inner perimeter, like the top of domed objects-
|
||||
|
||||
@ -275,16 +279,17 @@ void PrintObject::prepare_infill()
|
||||
|
||||
m_print->set_status(30, _u8L("Preparing infill"));
|
||||
|
||||
if (m_typed_slices) {
|
||||
// To improve robustness of detect_surfaces_type() when reslicing (working with typed slices), see GH issue #7442.
|
||||
// The preceding step (perimeter generator) only modifies extra_perimeters and the extra perimeters are only used by discover_vertical_shells()
|
||||
// with more than a single region. If this step does not use Surface::extra_perimeters or Surface::extra_perimeters is always zero, it is safe
|
||||
// to reset to the untyped slices before re-runnning detect_surfaces_type().
|
||||
for (Layer* layer : m_layers) {
|
||||
layer->restore_untyped_slices_no_extra_perimeters();
|
||||
m_print->throw_if_canceled();
|
||||
}
|
||||
}
|
||||
// if (m_typed_slices) {
|
||||
// // To improve robustness of detect_surfaces_type() when reslicing (working with typed slices), see GH issue #7442.
|
||||
// // The preceding step (perimeter generator) only modifies extra_perimeters and the extra perimeters are only used by discover_vertical_shells()
|
||||
// // with more than a single region. If this step does not use Surface::extra_perimeters or Surface::extra_perimeters is always zero, it is safe
|
||||
// // to reset to the untyped slices before re-runnning detect_surfaces_type().
|
||||
// for (Layer* layer : m_layers) {
|
||||
// layer->restore_untyped_slices_no_extra_perimeters();
|
||||
// m_print->throw_if_canceled();
|
||||
// }
|
||||
// this->detect_nonplanar_surfaces();
|
||||
// }
|
||||
|
||||
// This will assign a type (top/bottom/internal) to $layerm->slices.
|
||||
// Then the classifcation of $layerm->slices is transfered onto
|
||||
@ -434,6 +439,9 @@ void PrintObject::infill()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this->project_nonplanar_surfaces();
|
||||
|
||||
m_print->throw_if_canceled();
|
||||
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - end";
|
||||
/* we could free memory now, but this would make this step not idempotent
|
||||
@ -792,6 +800,13 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
steps.emplace_back(posInfill);
|
||||
steps.emplace_back(posSupportMaterial);
|
||||
}
|
||||
} else if (
|
||||
opt_key == "use_nonplanar_layers"
|
||||
|| opt_key == "nonplanar_layers_angle"
|
||||
|| opt_key == "nonplanar_layers_height") {
|
||||
// steps.emplace_back(posPerimeters);
|
||||
// steps.emplace_back(posInfill);
|
||||
steps.emplace_back(posSlice);
|
||||
} else if (
|
||||
opt_key == "perimeter_generator"
|
||||
|| opt_key == "wall_transition_length"
|
||||
@ -899,6 +914,8 @@ void PrintObject::cleanup()
|
||||
// stBottomBridge - Part of a region, which is not fully supported, but it hangs in the air, or it hangs losely on a support or a raft.
|
||||
// stBottom - Part of a region, which is not supported by the same region, but it is supported either by another region, or by a soluble interface layer.
|
||||
// stInternal - Part of a region, which is supported by the same region type.
|
||||
// stTopNonplanar -
|
||||
// stInternalSolidNonplanar -
|
||||
// If a part of a region is of stBottom and stTop, the stBottom wins.
|
||||
void PrintObject::detect_surfaces_type()
|
||||
{
|
||||
@ -952,6 +969,22 @@ void PrintObject::detect_surfaces_type()
|
||||
// collapse very narrow parts (using the safety offset in the diff is not enough)
|
||||
float offset = layerm->flow(frExternalPerimeter).scaled_width() / 10.f;
|
||||
|
||||
//Find mark nonplanar surfaces
|
||||
Surfaces nonplanar_surfaces;
|
||||
for(auto& surface : layerm->nonplanar_surfaces()) {
|
||||
surfaces_append(
|
||||
nonplanar_surfaces,
|
||||
intersection_ex(surface.horizontal_projection(), union_ex(layerm->slices().surfaces)),
|
||||
(surface.stats.max.z <= layer->slice_z + layer->height ? stTopNonplanar : stInternalSolidNonplanar)
|
||||
);
|
||||
BOOST_LOG_TRIVIAL(trace) << "Detecting solid surfaces - layer " << (idx_layer+1) << "/" << m_layers.size() << " is " <<
|
||||
(surface.stats.max.z <= layer->slice_z + layer->height ? "top nonplanar" : "internal nonplanar") <<
|
||||
", surface max z=" << surface.stats.max.z << ", slice_z=" << layer->slice_z << ", layer height=" << layer->height;
|
||||
}
|
||||
|
||||
//remove non planar surfaces form all surfaces to get planar surfaces
|
||||
ExPolygons planar_surfaces = diff_ex(layerm->slices().surfaces, nonplanar_surfaces, ApplySafetyOffset::Yes);
|
||||
|
||||
// find top surfaces (difference between current surfaces
|
||||
// of current layer and upper one)
|
||||
Surfaces top;
|
||||
@ -959,13 +992,11 @@ void PrintObject::detect_surfaces_type()
|
||||
ExPolygons upper_slices = interface_shells ?
|
||||
diff_ex(layerm->slices().surfaces, upper_layer->m_regions[region_id]->slices().surfaces, ApplySafetyOffset::Yes) :
|
||||
diff_ex(layerm->slices().surfaces, upper_layer->lslices, ApplySafetyOffset::Yes);
|
||||
surfaces_append(top, opening_ex(upper_slices, offset), stTop);
|
||||
surfaces_append(top, diff_ex(opening_ex(upper_slices, offset), nonplanar_surfaces, ApplySafetyOffset::Yes), stTop);
|
||||
} else {
|
||||
// if no upper layer, all surfaces of this one are solid
|
||||
// we clone surfaces because we're going to clear the slices collection
|
||||
top = layerm->slices().surfaces;
|
||||
for (Surface &surface : top)
|
||||
surface.surface_type = stTop;
|
||||
surfaces_append(top, union_ex(planar_surfaces), stTop);
|
||||
}
|
||||
|
||||
// Find bottom surfaces (difference between current surfaces of current layer and lower one).
|
||||
@ -984,7 +1015,11 @@ void PrintObject::detect_surfaces_type()
|
||||
surfaces_append(
|
||||
bottom,
|
||||
opening_ex(
|
||||
diff_ex(
|
||||
diff_ex(layerm->slices().surfaces, lower_layer->lslices, ApplySafetyOffset::Yes),
|
||||
nonplanar_surfaces,
|
||||
ApplySafetyOffset::Yes
|
||||
),
|
||||
offset),
|
||||
surface_type_bottom_other);
|
||||
// if user requested internal shells, we need to identify surfaces
|
||||
@ -995,10 +1030,13 @@ void PrintObject::detect_surfaces_type()
|
||||
surfaces_append(
|
||||
bottom,
|
||||
opening_ex(
|
||||
diff_ex(
|
||||
diff_ex(
|
||||
intersection(layerm->slices().surfaces, lower_layer->lslices), // supported
|
||||
lower_layer->m_regions[region_id]->slices().surfaces,
|
||||
ApplySafetyOffset::Yes),
|
||||
nonplanar_surfaces,
|
||||
ApplySafetyOffset::Yes),
|
||||
offset),
|
||||
stBottom);
|
||||
}
|
||||
@ -1029,6 +1067,7 @@ void PrintObject::detect_surfaces_type()
|
||||
std::vector<std::pair<Slic3r::ExPolygons, SVG::ExPolygonAttributes>> expolygons_with_attributes;
|
||||
expolygons_with_attributes.emplace_back(std::make_pair(union_ex(top), SVG::ExPolygonAttributes("green")));
|
||||
expolygons_with_attributes.emplace_back(std::make_pair(union_ex(bottom), SVG::ExPolygonAttributes("brown")));
|
||||
expolygons_with_attributes.emplace_back(std::make_pair(union_ex(nonplanar_surfaces), SVG::ExPolygonAttributes("red")));
|
||||
expolygons_with_attributes.emplace_back(std::make_pair(to_expolygons(layerm->slices().surfaces), SVG::ExPolygonAttributes("black")));
|
||||
SVG::export_expolygons(debug_out_path("1_detect_surfaces_type_%d_region%d-layer_%f.svg", iRun ++, region_id, layer->print_z).c_str(), expolygons_with_attributes);
|
||||
}
|
||||
@ -1045,13 +1084,15 @@ void PrintObject::detect_surfaces_type()
|
||||
|
||||
// find internal surfaces (difference between top/bottom surfaces and others)
|
||||
{
|
||||
Polygons topbottom = to_polygons(top);
|
||||
polygons_append(topbottom, to_polygons(bottom));
|
||||
surfaces_append(surfaces_out, diff_ex(surfaces_prev, topbottom), stInternal);
|
||||
Polygons solid_surfaces = to_polygons(top);
|
||||
polygons_append(solid_surfaces, to_polygons(bottom));
|
||||
polygons_append(solid_surfaces, to_polygons(nonplanar_surfaces));
|
||||
surfaces_append(surfaces_out, diff_ex(surfaces_prev, solid_surfaces), stInternal);
|
||||
}
|
||||
|
||||
surfaces_append(surfaces_out, std::move(top));
|
||||
surfaces_append(surfaces_out, std::move(bottom));
|
||||
surfaces_append(surfaces_out, std::move(nonplanar_surfaces));
|
||||
|
||||
// Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n",
|
||||
// $layerm->layer->id, scalar(@bottom), scalar(@top), scalar(@internal) if $Slic3r::debug;
|
||||
@ -1101,6 +1142,240 @@ void PrintObject::detect_surfaces_type()
|
||||
m_typed_slices = true;
|
||||
}
|
||||
|
||||
bool
|
||||
PrintObject::check_nonplanar_collisions(NonplanarSurface &surface)
|
||||
{
|
||||
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
|
||||
Polygons collider;
|
||||
Polygons nonplanar_polygon = to_polygons(surface.horizontal_projection());
|
||||
//check each layer
|
||||
for (size_t idx_layer = 0; idx_layer < m_layers.size(); ++ idx_layer) {
|
||||
m_print->throw_if_canceled();
|
||||
// BOOST_LOG_TRIVIAL(trace) << "Check nonplanar collisions for region " << region_id << " and layer " << layer->print_z;
|
||||
Layer *layer = m_layers[idx_layer];
|
||||
LayerRegion *layerm = layer->m_regions[region_id];
|
||||
|
||||
//skip if below minimum nonplanar surface
|
||||
if (surface.stats.min.z-layer->height > layer->slice_z) continue;
|
||||
//break if above nonplanar surface
|
||||
if (surface.stats.max.z < layer->slice_z) break;
|
||||
|
||||
float angle_rad = m_config.nonplanar_layers_angle.value * 3.14159265/180.0;
|
||||
float angle_offset = scale_(layer->height*std::sin(1.57079633-angle_rad)/std::sin(angle_rad));
|
||||
|
||||
//debug
|
||||
// SVG svg("svg/collider" + std::to_string(layer->id()) + ".svg");
|
||||
// svg.draw(layerm_slices_surfaces, "blue");
|
||||
// svg.draw(union_ex(diff(collider,nonplanar_polygon)), "red", 0.7f);
|
||||
// svg.draw_outline(collider);
|
||||
// svg.arrows = false;
|
||||
// svg.Close();
|
||||
|
||||
//check if current surface collides with previous collider
|
||||
ExPolygons collisions = union_ex(intersection(layerm->slices().surfaces, diff(collider, nonplanar_polygon)));
|
||||
|
||||
if (!collisions.empty()){
|
||||
double area = 0;
|
||||
for (auto& c : collisions){
|
||||
area += c.area();
|
||||
}
|
||||
|
||||
//collsion found abort when area > 1.0 mm²
|
||||
if (1.0 < unscale<double>(unscale<double>(area))) {
|
||||
std::cout << "Surface removed: collision on layer " << layer->print_z << "mm (" << unscale<double>(unscale<double>(area)) << " mm²)" << '\n';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (layer->upper_layer != NULL) {
|
||||
Layer* upper_layer = layer->upper_layer;
|
||||
LayerRegion *upper_layerm = upper_layer->m_regions[region_id];
|
||||
//merge the ofsetted surface to the collider
|
||||
collider= offset(
|
||||
union_(
|
||||
intersection(
|
||||
diff_ex(layerm->slices().surfaces, upper_layerm->slices().surfaces, ApplySafetyOffset::No),
|
||||
nonplanar_polygon,
|
||||
ApplySafetyOffset::No),
|
||||
collider),
|
||||
angle_offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
PrintObject::detect_nonplanar_surfaces()
|
||||
{
|
||||
//skip if not active
|
||||
if(!m_config.use_nonplanar_layers.value) return;
|
||||
|
||||
bool moved_surfaces = false;
|
||||
|
||||
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
|
||||
m_print->throw_if_canceled();
|
||||
BOOST_LOG_TRIVIAL(trace) << "detect_nonplanar_surfaces for region " << region_id;
|
||||
const PrintRegion ®ion = this->printing_region(region_id);
|
||||
|
||||
//repeat detection for every nonplanar_surface
|
||||
for (auto& nonplanar_surface: this->nonplanar_surfaces()) {
|
||||
float distance_to_top = 0.0f;
|
||||
for (int shell_thickness = 0; region.config().top_solid_layers > shell_thickness; ++shell_thickness){
|
||||
//search home layer where the area is projected to
|
||||
for (LayerPtrs::reverse_iterator home_layer_it = this->m_layers.rbegin(); home_layer_it != this->m_layers.rend(); ++home_layer_it){
|
||||
Layer* home_layer = *home_layer_it;
|
||||
LayerRegion &home_layerm = *home_layer->m_regions[region_id];
|
||||
//continue if home layer is not maximum height of nonplanar_surface - the desired distance to the top of the surface for more than one top solid layer
|
||||
if (home_layer->slice_z > nonplanar_surface.stats.max.z - distance_to_top) continue;
|
||||
|
||||
//process layers
|
||||
for (LayerPtrs::iterator layer_it = this->m_layers.begin(); layer_it != this->m_layers.end(); ++layer_it){
|
||||
Layer* layer = *layer_it;
|
||||
LayerRegion &layerm = *layer->m_regions[region_id];
|
||||
|
||||
//skip if below minimum nonplanar surface and below the last possible surface layer
|
||||
if (nonplanar_surface.stats.min.z-layer->height-distance_to_top > layer->slice_z) continue;
|
||||
//break if above home layer
|
||||
if (home_layer->slice_z < layer->slice_z) break;
|
||||
//skip if bottom layer because we dont want to project the bottom layers up
|
||||
if (layer->lower_layer == NULL) continue;
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "detect_nonplanar_surfaces for region " << region_id << " and layer " << layer->print_z;
|
||||
|
||||
Surfaces layerm_slices_surfaces = layerm.slices().surfaces;
|
||||
SurfaceCollection topNonplanar;
|
||||
if (layer->upper_layer != NULL) {
|
||||
//append layers where nothing is above
|
||||
Layer* upper_layer = layer->upper_layer;
|
||||
LayerRegion &upper_layerm = *upper_layer->m_regions[region_id];
|
||||
Surfaces upper_surfaces = upper_layerm.slices().surfaces;
|
||||
topNonplanar.append(
|
||||
intersection_ex(
|
||||
nonplanar_surface.horizontal_projection(),
|
||||
union_ex(
|
||||
diff_ex(
|
||||
layerm_slices_surfaces,
|
||||
upper_surfaces,
|
||||
ApplySafetyOffset::No)),
|
||||
ApplySafetyOffset::No),
|
||||
(shell_thickness == 0 ? stTopNonplanar : stInternalSolidNonplanar),
|
||||
distance_to_top
|
||||
);
|
||||
|
||||
// append layers where nonplanar areas with a lower distance_to_top are above
|
||||
SurfaceCollection upper_nonplanar;
|
||||
for (auto& s : upper_surfaces){
|
||||
if (s.is_nonplanar() && s.distance_to_top < distance_to_top) {
|
||||
upper_nonplanar.surfaces.push_back(s);
|
||||
}
|
||||
}
|
||||
if (upper_nonplanar.size() > 0)
|
||||
topNonplanar.append(
|
||||
intersection_ex(
|
||||
nonplanar_surface.horizontal_projection(),
|
||||
to_expolygons(upper_nonplanar.surfaces),
|
||||
ApplySafetyOffset::No),
|
||||
(shell_thickness == 0 ? stTopNonplanar : stInternalSolidNonplanar),
|
||||
distance_to_top
|
||||
);
|
||||
}
|
||||
else {
|
||||
topNonplanar.append(
|
||||
intersection_ex(
|
||||
nonplanar_surface.horizontal_projection(),
|
||||
union_ex(to_expolygons(layerm_slices_surfaces)),
|
||||
ApplySafetyOffset::No),
|
||||
(shell_thickness == 0 ? stTopNonplanar : stInternalSolidNonplanar),
|
||||
distance_to_top
|
||||
);
|
||||
}
|
||||
|
||||
if (topNonplanar.size() > 0) {
|
||||
// layerm.export_region_slices_to_svg_debug("home_layer_append-layerm");
|
||||
// home_layerm.export_region_slices_to_svg_debug("home_layer_append-home_layerm");
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Removing " << topNonplanar.size() << " nonplanar surfaces from layer " << layer->print_z;
|
||||
|
||||
layerm.remove_nonplanar_slices(topNonplanar);
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Adding " << topNonplanar.size() << " nonplanar surfaces to layer " << home_layer->print_z;
|
||||
|
||||
// move nonplanar surfaces to home layer
|
||||
home_layerm.append_top_nonplanar_slices(topNonplanar);
|
||||
|
||||
//save nonplanar_surface to home_layers nonplanar_surface list
|
||||
home_layerm.append_nonplanar_surface(nonplanar_surface);
|
||||
|
||||
moved_surfaces = true;
|
||||
}
|
||||
}
|
||||
|
||||
//increase distance to the top layer
|
||||
distance_to_top += home_layer->height;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(moved_surfaces) {
|
||||
// After changing a layer's slices, we must rebuild its lslices into islands
|
||||
this->make_slices();
|
||||
this->lslices_were_updated();
|
||||
}
|
||||
|
||||
// Debugging output.
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
|
||||
for (const Layer *layer : m_layers) {
|
||||
LayerRegion *layerm = layer->m_regions[region_id];
|
||||
layerm->export_region_slices_to_svg_debug("0_detect_nonplanar_surfaces");
|
||||
layerm->export_region_fill_surfaces_to_svg_debug("0_detect_nonplanar_surfaces");
|
||||
} // for each layer
|
||||
} // for each region
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
//set typed_slices to true to force merge
|
||||
m_typed_slices = true;
|
||||
}
|
||||
|
||||
void
|
||||
PrintObject::project_nonplanar_surfaces()
|
||||
{
|
||||
if(!m_config.use_nonplanar_layers.value) return;
|
||||
|
||||
//TODO check when steps should be invalidated
|
||||
if (is_step_done(posNonplanarProjection)) return;
|
||||
set_started(posNonplanarProjection);
|
||||
|
||||
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Processing nonplanar surfaces for region " << region_id << " in parallel - start";
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||
[this, region_id](const tbb::blocked_range<size_t>& range) {
|
||||
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
|
||||
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
||||
m_print->throw_if_canceled();
|
||||
LayerRegion *layerm = m_layers[idx_layer]->m_regions[region_id];
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Processing nonplanar surfaces for region " << region_id << " and layer " << \
|
||||
idx_layer << " (z = " << layerm->layer()->print_z << ")";
|
||||
|
||||
layerm->project_nonplanar_surfaces();
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
layerm->export_region_fill_surfaces_to_svg_debug("11_project_nonplanar_surfaces-final");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
} // for each layer of a region
|
||||
});
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Processing nonplanar surfaces - end";
|
||||
|
||||
set_done(posNonplanarProjection);
|
||||
}
|
||||
|
||||
void PrintObject::process_external_surfaces()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "Processing external surfaces..." << log_memory_info();
|
||||
@ -1223,6 +1498,7 @@ void PrintObject::discover_vertical_shells()
|
||||
[this, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
|
||||
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
|
||||
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
|
||||
const std::initializer_list<SurfaceType> surfaces_top { stTop, stTopNonplanar };
|
||||
const size_t num_regions = this->num_printing_regions();
|
||||
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
||||
m_print->throw_if_canceled();
|
||||
@ -1239,7 +1515,7 @@ void PrintObject::discover_vertical_shells()
|
||||
LayerRegion &layerm = *layer.m_regions[region_id];
|
||||
float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
|
||||
// Top surfaces.
|
||||
append(cache.top_surfaces, offset(layerm.slices().filter_by_type(stTop), top_bottom_expansion));
|
||||
append(cache.top_surfaces, offset(layerm.slices().filter_by_types(surfaces_top), top_bottom_expansion));
|
||||
// append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), top_bottom_expansion));
|
||||
// Bottom surfaces.
|
||||
append(cache.bottom_surfaces, offset(layerm.slices().filter_by_types(surfaces_bottom), top_bottom_expansion));
|
||||
@ -1297,6 +1573,7 @@ void PrintObject::discover_vertical_shells()
|
||||
[this, region_id, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
|
||||
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
|
||||
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
|
||||
const std::initializer_list<SurfaceType> surfaces_top { stTop, stTopNonplanar };
|
||||
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
||||
m_print->throw_if_canceled();
|
||||
Layer &layer = *m_layers[idx_layer];
|
||||
@ -1304,7 +1581,7 @@ void PrintObject::discover_vertical_shells()
|
||||
float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
|
||||
// Top surfaces.
|
||||
auto &cache = cache_top_botom_regions[idx_layer];
|
||||
cache.top_surfaces = offset(layerm.slices().filter_by_type(stTop), top_bottom_expansion);
|
||||
cache.top_surfaces = offset(layerm.slices().filter_by_types(surfaces_top), top_bottom_expansion);
|
||||
// append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), top_bottom_expansion));
|
||||
// Bottom surfaces.
|
||||
cache.bottom_surfaces = offset(layerm.slices().filter_by_types(surfaces_bottom), top_bottom_expansion);
|
||||
@ -1502,17 +1779,31 @@ void PrintObject::discover_vertical_shells()
|
||||
svg.draw_outline(shell_ex, "black", "blue", scale_(0.05));
|
||||
svg.Close();
|
||||
}
|
||||
{
|
||||
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalsolidnonplanar-wshell-%d.svg", debug_idx), get_extents(shell));
|
||||
svg.draw(layerm->fill_surfaces().filter_by_type(stInternalSolidNonplanar), "yellow", 0.5);
|
||||
svg.draw_outline(layerm->fill_surfaces().filter_by_type(stInternalSolidNonplanar), "black", "blue", scale_(0.05));
|
||||
svg.draw(shell_ex, "blue", 0.5);
|
||||
svg.draw_outline(shell_ex, "black", "blue", scale_(0.05));
|
||||
svg.Close();
|
||||
}
|
||||
{
|
||||
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-holes-wshell-%d.svg", debug_idx), get_extents(shell));
|
||||
svg.draw_outline(to_expolygons(holes), "black", "blue", scale_(0.05));
|
||||
svg.Close();
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
// Trim the shells region by the internal & internal void surfaces.
|
||||
const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces().filter_by_types({ stInternal, stInternalVoid, stInternalSolid }));
|
||||
const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces().filter_by_types({ stInternal, stInternalVoid, stInternalSolid, stInternalSolidNonplanar }));
|
||||
const Polygons polygonsInternalNonplanar = to_polygons(layerm->fill_surfaces().filter_by_type(stInternalSolidNonplanar));
|
||||
shell = intersection(shell, polygonsInternal, ApplySafetyOffset::Yes);
|
||||
polygons_append(shell, diff(polygonsInternal, holes));
|
||||
if (shell.empty())
|
||||
continue;
|
||||
|
||||
// Append the internal solids, so they will be merged with the new ones.
|
||||
polygons_append(shell, to_polygons(layerm->fill_surfaces().filter_by_type(stInternalSolid)));
|
||||
polygons_append(shell, to_polygons(layerm->fill_surfaces().filter_by_types({ stInternalSolid, stInternalSolidNonplanar })));
|
||||
|
||||
// These regions will be filled by a rectilinear full infill. Currently this type of infill
|
||||
// only fills regions, which fit at least a single line. To avoid gaps in the sparse infill,
|
||||
@ -1560,7 +1851,8 @@ void PrintObject::discover_vertical_shells()
|
||||
if (regularized_shell.empty())
|
||||
continue;
|
||||
|
||||
ExPolygons new_internal_solid = intersection_ex(polygonsInternal, regularized_shell);
|
||||
ExPolygons new_internal_solid = intersection_ex(diff_ex(polygonsInternal, polygonsInternalNonplanar), regularized_shell);
|
||||
ExPolygons new_internal_solid_nonplanar = intersection_ex(polygonsInternalNonplanar, regularized_shell);
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-regularized-%d.svg", debug_idx), get_extents(shell_before));
|
||||
@ -1583,14 +1875,16 @@ void PrintObject::discover_vertical_shells()
|
||||
SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal-%d.svg", debug_idx), get_extents(shell), new_internal, "black", "blue", scale_(0.05));
|
||||
SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal_void-%d.svg", debug_idx), get_extents(shell), new_internal_void, "black", "blue", scale_(0.05));
|
||||
SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal_solid-%d.svg", debug_idx), get_extents(shell), new_internal_solid, "black", "blue", scale_(0.05));
|
||||
SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal_solid_nonplanar-%d.svg", debug_idx), get_extents(shell), new_internal_solid_nonplanar, "black", "blue", scale_(0.05));
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
// Assign resulting internal surfaces to layer.
|
||||
layerm->m_fill_surfaces.keep_types({ stTop, stBottom, stBottomBridge });
|
||||
layerm->m_fill_surfaces.keep_types({ stTop, stTopNonplanar, stBottom, stBottomBridge });
|
||||
layerm->m_fill_surfaces.append(new_internal, stInternal);
|
||||
layerm->m_fill_surfaces.append(new_internal_void, stInternalVoid);
|
||||
layerm->m_fill_surfaces.append(new_internal_solid, stInternalSolid);
|
||||
layerm->m_fill_surfaces.append(new_internal_solid_nonplanar, stInternalSolidNonplanar);
|
||||
} // for each layer
|
||||
});
|
||||
m_print->throw_if_canceled();
|
||||
@ -1688,7 +1982,7 @@ void PrintObject::bridge_over_infill()
|
||||
unsupported_area = diff(unsupported_area, lower_layer_solids);
|
||||
|
||||
for (const LayerRegion *region : layer->regions()) {
|
||||
SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid);
|
||||
SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_types({stInternalSolid, stInternalSolidNonplanar});
|
||||
for (const Surface *s : region_internal_solids) {
|
||||
Polygons unsupported = intersection(to_polygons(s->expolygon), unsupported_area);
|
||||
// The following flag marks those surfaces, which overlap with unuspported area, but at least part of them is supported.
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "MultiMaterialSegmentation.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "ShortestPath.hpp"
|
||||
#include "AABBMesh.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
@ -518,6 +519,14 @@ void PrintObject::slice()
|
||||
if (! warning.empty())
|
||||
BOOST_LOG_TRIVIAL(info) << warning;
|
||||
#endif
|
||||
this->lslices_were_updated();
|
||||
if (m_layers.empty())
|
||||
throw Slic3r::SlicingError("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);
|
||||
}
|
||||
|
||||
void PrintObject::lslices_were_updated()
|
||||
{
|
||||
// Update bounding boxes, back up raw slices of complex models.
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||
@ -541,9 +550,6 @@ void PrintObject::slice()
|
||||
Layer::build_up_down_graph(*m_layers[layer_idx - 1], *m_layers[layer_idx]);
|
||||
}
|
||||
});
|
||||
if (m_layers.empty())
|
||||
throw Slic3r::SlicingError("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);
|
||||
}
|
||||
|
||||
template<typename ThrowOnCancel>
|
||||
@ -740,7 +746,16 @@ void PrintObject::slice_volumes()
|
||||
apply_mm_segmentation(*this, [print]() { print->throw_if_canceled(); });
|
||||
}
|
||||
|
||||
this->find_nonplanar_surfaces();
|
||||
|
||||
this->make_slices();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - end";
|
||||
}
|
||||
|
||||
void PrintObject::make_slices()
|
||||
{
|
||||
const Print *print = this->print();
|
||||
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin";
|
||||
{
|
||||
// Compensation value, scaled. Only applying the negative scaling here, as the positive scaling has already been applied during slicing.
|
||||
@ -802,6 +817,7 @@ void PrintObject::slice_volumes()
|
||||
layer->m_regions[region_id]->trim_surfaces(trimming);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge all regions' slices to get islands sorted topologically, chain them by a shortest path in separate index list
|
||||
layer->make_slices();
|
||||
}
|
||||
@ -822,6 +838,106 @@ void PrintObject::slice_volumes()
|
||||
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - end";
|
||||
}
|
||||
|
||||
void
|
||||
PrintObject::find_nonplanar_surfaces()
|
||||
{
|
||||
//skip if not active
|
||||
if(!m_config.use_nonplanar_layers.value) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Find nonplanar surfaces - disabled";
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Find nonplanar surfaces - start";
|
||||
|
||||
//Itterate over all model volumes
|
||||
const ModelVolumePtrs volumes = this->model_object()->volumes;
|
||||
for (ModelVolumePtrs::const_iterator it = volumes.begin(); it != volumes.end(); ++ it) {
|
||||
//only check non modifier volumes
|
||||
if (! (*it)->is_modifier()) {
|
||||
const TriangleMesh tmesh = (*it)->mesh();
|
||||
AABBMesh mesh(tmesh, true);
|
||||
std::map<int, NonplanarFacet> facets;
|
||||
|
||||
// store all meshes with slope <= nonplanar_layers_angle in map. Map is necessary to keep facet ID
|
||||
for (int face_id = 0; face_id < mesh.indices().size(); ++ face_id) {
|
||||
auto &face = mesh.indices(face_id);
|
||||
Vec3d normal = mesh.normal_by_face_id(face_id);
|
||||
|
||||
//TODO check if normals exist
|
||||
if (normal.z() >= std::cos(m_config.nonplanar_layers_angle.value * 3.14159265/180.0)) {
|
||||
//copy facet
|
||||
NonplanarFacet new_facet;
|
||||
new_facet.normal.x = normal.x();
|
||||
new_facet.normal.y = normal.y();
|
||||
new_facet.normal.z = normal.z();
|
||||
|
||||
Vec3i neighbor = mesh.face_neighbor_index()[face_id];
|
||||
its_triangle vertex = its_triangle_vertices(*mesh.get_triangle_mesh(), face_id);
|
||||
for (int j=0; j<=2 ;j++) {
|
||||
new_facet.vertex[j].x = vertex[j].x();
|
||||
new_facet.vertex[j].y = vertex[j].y();
|
||||
new_facet.vertex[j].z = vertex[j].z();
|
||||
new_facet.neighbor[j] = neighbor[j];
|
||||
}
|
||||
new_facet.calculate_stats();
|
||||
facets[face_id] = new_facet;
|
||||
}
|
||||
}
|
||||
|
||||
// create nonplanar surface from facets
|
||||
NonplanarSurface nf = NonplanarSurface(facets);
|
||||
BOOST_LOG_TRIVIAL(trace) << "Find nonplanar surfaces - moving surfaces by z=" << -tmesh.stats().min.z();
|
||||
nf.translate(0, 0, -tmesh.stats().min.z());
|
||||
|
||||
// group surfaces and attach all nonplanar surfaces to the PrintObject
|
||||
m_nonplanar_surfaces = nf.group_surfaces();
|
||||
|
||||
// check if surfaces maintain maximum printing height, if not, erase it
|
||||
for (NonplanarSurfaces::iterator it = m_nonplanar_surfaces.begin(); it!=m_nonplanar_surfaces.end();) {
|
||||
if((*it).check_max_printing_height(m_config.nonplanar_layers_height.value)) {
|
||||
it = m_nonplanar_surfaces.erase(it);
|
||||
}else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
// check if surfaces area is not too small
|
||||
for (NonplanarSurfaces::iterator it = m_nonplanar_surfaces.begin(); it!=m_nonplanar_surfaces.end();) {
|
||||
if((*it).check_surface_area()) {
|
||||
it = m_nonplanar_surfaces.erase(it);
|
||||
}else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
// check if surfaces areas collide
|
||||
for (NonplanarSurfaces::iterator it = m_nonplanar_surfaces.begin(); it!=m_nonplanar_surfaces.end();) {
|
||||
if(check_nonplanar_collisions((*it))) {
|
||||
it = m_nonplanar_surfaces.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
//nf.debug_output();
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
for (size_t id = 0; id < m_nonplanar_surfaces.size(); ++ id) {
|
||||
auto& surface = m_nonplanar_surfaces[id];
|
||||
Surfaces surfaces;
|
||||
surfaces_append(surfaces, surface.horizontal_projection(), SurfaceType::stTopNonplanar);
|
||||
SurfaceCollection c(surfaces);
|
||||
c.export_to_svg(debug_out_path("0_find_nonplanar_surface-%d.svg", id).c_str(), true);
|
||||
}
|
||||
#endif
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Find nonplanar surfaces - found " << m_nonplanar_surfaces.size() << " in " << (*it)->name;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Find nonplanar surfaces - end";
|
||||
}
|
||||
|
||||
std::vector<Polygons> PrintObject::slice_support_volumes(const ModelVolumeType model_volume_type) const
|
||||
{
|
||||
auto it_volume = this->model_object()->volumes.begin();
|
||||
|
@ -243,9 +243,9 @@ float get_flow_width(const LayerRegion *region, ExtrusionRole role)
|
||||
if (role == ExtrusionRole::ExternalPerimeter) return region->flow(FlowRole::frExternalPerimeter).width();
|
||||
if (role == ExtrusionRole::GapFill) return region->flow(FlowRole::frInfill).width();
|
||||
if (role == ExtrusionRole::Perimeter) return region->flow(FlowRole::frPerimeter).width();
|
||||
if (role == ExtrusionRole::SolidInfill) return region->flow(FlowRole::frSolidInfill).width();
|
||||
if (role == ExtrusionRole::SolidInfill || role == ExtrusionRole::SolidInfillNonplanar) return region->flow(FlowRole::frSolidInfill).width();
|
||||
if (role == ExtrusionRole::InternalInfill) return region->flow(FlowRole::frInfill).width();
|
||||
if (role == ExtrusionRole::TopSolidInfill) return region->flow(FlowRole::frTopSolidInfill).width();
|
||||
if (role == ExtrusionRole::TopSolidInfill || role == ExtrusionRole::TopSolidInfillNonplanar) return region->flow(FlowRole::frTopSolidInfill).width();
|
||||
// default
|
||||
return region->flow(FlowRole::frPerimeter).width();
|
||||
}
|
||||
|
@ -35,10 +35,12 @@ const char* surface_type_to_color_name(const SurfaceType surface_type)
|
||||
{
|
||||
switch (surface_type) {
|
||||
case stTop: return "rgb(255,0,0)"; // "red";
|
||||
case stTopNonplanar: return "rgb(166,2,255)"; // "purple";
|
||||
case stBottom: return "rgb(0,255,0)"; // "green";
|
||||
case stBottomBridge: return "rgb(0,0,255)"; // "blue";
|
||||
case stInternal: return "rgb(255,255,128)"; // yellow
|
||||
case stInternalSolid: return "rgb(255,0,255)"; // magenta
|
||||
case stInternalSolidNonplanar: return "rgb(255,133,2)"; // orange
|
||||
case stInternalBridge: return "rgb(0,255,255)";
|
||||
case stInternalVoid: return "rgb(128,128,128)";
|
||||
case stPerimeter: return "rgb(128,0,0)"; // maroon
|
||||
@ -48,7 +50,7 @@ const char* surface_type_to_color_name(const SurfaceType surface_type)
|
||||
|
||||
Point export_surface_type_legend_to_svg_box_size()
|
||||
{
|
||||
return Point(scale_(1.+10.*8.), scale_(3.));
|
||||
return Point(scale_(1.+16.*8.), scale_(3.));
|
||||
}
|
||||
|
||||
void export_surface_type_legend_to_svg(SVG &svg, const Point &pos)
|
||||
@ -57,11 +59,13 @@ void export_surface_type_legend_to_svg(SVG &svg, const Point &pos)
|
||||
coord_t pos_x0 = pos(0) + scale_(1.);
|
||||
coord_t pos_x = pos_x0;
|
||||
coord_t pos_y = pos(1) + scale_(1.5);
|
||||
coord_t step_x = scale_(10.);
|
||||
coord_t step_x = scale_(16.);
|
||||
svg.draw_legend(Point(pos_x, pos_y), "perimeter" , surface_type_to_color_name(stPerimeter));
|
||||
pos_x += step_x;
|
||||
svg.draw_legend(Point(pos_x, pos_y), "top" , surface_type_to_color_name(stTop));
|
||||
pos_x += step_x;
|
||||
svg.draw_legend(Point(pos_x, pos_y), "top nonplanar" , surface_type_to_color_name(stTopNonplanar));
|
||||
pos_x += step_x;
|
||||
svg.draw_legend(Point(pos_x, pos_y), "bottom" , surface_type_to_color_name(stBottom));
|
||||
pos_x += step_x;
|
||||
svg.draw_legend(Point(pos_x, pos_y), "bottom bridge" , surface_type_to_color_name(stBottomBridge));
|
||||
@ -74,6 +78,8 @@ void export_surface_type_legend_to_svg(SVG &svg, const Point &pos)
|
||||
pos_x += step_x;
|
||||
svg.draw_legend(Point(pos_x, pos_y), "internal solid" , surface_type_to_color_name(stInternalSolid));
|
||||
pos_x += step_x;
|
||||
svg.draw_legend(Point(pos_x, pos_y), "internal solid nonplanar" , surface_type_to_color_name(stInternalSolidNonplanar));
|
||||
pos_x += step_x;
|
||||
svg.draw_legend(Point(pos_x, pos_y), "internal bridge", surface_type_to_color_name(stInternalBridge));
|
||||
pos_x += step_x;
|
||||
svg.draw_legend(Point(pos_x, pos_y), "internal void" , surface_type_to_color_name(stInternalVoid));
|
||||
|
@ -24,6 +24,10 @@ enum SurfaceType {
|
||||
stInternalVoid,
|
||||
// Inner/outer perimeters.
|
||||
stPerimeter,
|
||||
//
|
||||
stTopNonplanar,
|
||||
//
|
||||
stInternalSolidNonplanar,
|
||||
// Number of SurfaceType enums.
|
||||
stCount,
|
||||
};
|
||||
@ -37,6 +41,7 @@ public:
|
||||
unsigned short thickness_layers { 1 }; // in layers
|
||||
double bridge_angle { -1. }; // in radians, ccw, 0 = East, only 0+ (negative means undefined)
|
||||
unsigned short extra_perimeters { 0 };
|
||||
float distance_to_top { 0 };
|
||||
|
||||
Surface(const Slic3r::Surface &rhs) :
|
||||
surface_type(rhs.surface_type), expolygon(rhs.expolygon),
|
||||
@ -67,6 +72,7 @@ public:
|
||||
thickness_layers = rhs.thickness_layers;
|
||||
bridge_angle = rhs.bridge_angle;
|
||||
extra_perimeters = rhs.extra_perimeters;
|
||||
distance_to_top = rhs.distance_to_top;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -78,6 +84,7 @@ public:
|
||||
thickness_layers = rhs.thickness_layers;
|
||||
bridge_angle = rhs.bridge_angle;
|
||||
extra_perimeters = rhs.extra_perimeters;
|
||||
distance_to_top = rhs.distance_to_top;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -86,12 +93,14 @@ public:
|
||||
void clear() { expolygon.clear(); }
|
||||
|
||||
// The following methods do not test for stPerimeter.
|
||||
bool is_top() const { return this->surface_type == stTop; }
|
||||
bool is_top() const { return this->surface_type == stTop || this->surface_type == stTopNonplanar; }
|
||||
bool is_bottom() const { return this->surface_type == stBottom || this->surface_type == stBottomBridge; }
|
||||
bool is_bridge() const { return this->surface_type == stBottomBridge || this->surface_type == stInternalBridge; }
|
||||
bool is_external() const { return this->is_top() || this->is_bottom(); }
|
||||
bool is_internal() const { return ! this->is_external(); }
|
||||
bool is_solid() const { return this->is_external() || this->surface_type == stInternalSolid || this->surface_type == stInternalBridge; }
|
||||
bool is_internal_solid() const { return this->surface_type == stInternalSolid || this->surface_type == stInternalSolidNonplanar; }
|
||||
bool is_solid() const { return this->is_external() || this->surface_type == stInternalSolid || this->surface_type == stInternalSolidNonplanar || this->surface_type == stInternalBridge; }
|
||||
bool is_nonplanar() const { return this->surface_type == stTopNonplanar || this->surface_type == stInternalSolidNonplanar; }
|
||||
};
|
||||
|
||||
typedef std::vector<Surface> Surfaces;
|
||||
@ -228,9 +237,20 @@ inline void polygons_append(Polygons &dst, SurfacesPtr &&src)
|
||||
inline void surfaces_append(Surfaces &dst, const ExPolygons &src, SurfaceType surfaceType)
|
||||
{
|
||||
dst.reserve(dst.size() + src.size());
|
||||
for (const ExPolygon &expoly : src)
|
||||
for (const ExPolygon &expoly : src) {
|
||||
dst.emplace_back(Surface(surfaceType, expoly));
|
||||
}
|
||||
}
|
||||
inline void surfaces_append(Surfaces &dst, const ExPolygons &src, SurfaceType surfaceType, float distance_to_top)
|
||||
{
|
||||
dst.reserve(dst.size() + src.size());
|
||||
for (const ExPolygon &expoly : src) {
|
||||
Surface s = Surface(surfaceType, expoly);
|
||||
s.distance_to_top = distance_to_top;
|
||||
dst.emplace_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
inline void surfaces_append(Surfaces &dst, const ExPolygons &src, const Surface &surfaceTempl)
|
||||
{
|
||||
dst.reserve(dst.size() + number_polygons(src));
|
||||
@ -278,7 +298,8 @@ inline bool surfaces_could_merge(const Surface &s1, const Surface &s2)
|
||||
s1.surface_type == s2.surface_type &&
|
||||
s1.thickness == s2.thickness &&
|
||||
s1.thickness_layers == s2.thickness_layers &&
|
||||
s1.bridge_angle == s2.bridge_angle;
|
||||
s1.bridge_angle == s2.bridge_angle &&
|
||||
s1.distance_to_top == s2.distance_to_top;
|
||||
}
|
||||
|
||||
class SVG;
|
||||
|
@ -68,9 +68,11 @@ public:
|
||||
void append(const SurfaceCollection &coll) { this->append(coll.surfaces); }
|
||||
void append(SurfaceCollection &&coll) { this->append(std::move(coll.surfaces)); }
|
||||
void append(const ExPolygons &src, SurfaceType surfaceType) { surfaces_append(this->surfaces, src, surfaceType); }
|
||||
void append(const ExPolygons &src, SurfaceType surfaceType, float distance_to_top) { surfaces_append(this->surfaces, src, surfaceType, distance_to_top); }
|
||||
void append(const ExPolygons &src, const Surface &surfaceTempl) { surfaces_append(this->surfaces, src, surfaceTempl); }
|
||||
void append(const Surfaces &src) { surfaces_append(this->surfaces, src); }
|
||||
void append(ExPolygons &&src, SurfaceType surfaceType) { surfaces_append(this->surfaces, std::move(src), surfaceType); }
|
||||
void append(ExPolygons &&src, SurfaceType surfaceType, float distance_to_top) { surfaces_append(this->surfaces, std::move(src), surfaceType, distance_to_top); }
|
||||
void append(ExPolygons &&src, const Surface &surfaceTempl) { surfaces_append(this->surfaces, std::move(src), surfaceTempl); }
|
||||
void append(Surfaces &&src) { surfaces_append(this->surfaces, std::move(src)); }
|
||||
|
||||
|
@ -569,7 +569,9 @@ const std::array<ColorRGBA, static_cast<size_t>(GCodeExtrusionRole::Count)> GCod
|
||||
{ 0.12f, 0.12f, 1.00f, 1.0f }, // GCodeExtrusionRole::OverhangPerimeter
|
||||
{ 0.69f, 0.19f, 0.16f, 1.0f }, // GCodeExtrusionRole::InternalInfill
|
||||
{ 0.59f, 0.33f, 0.80f, 1.0f }, // GCodeExtrusionRole::SolidInfill
|
||||
{ 0.75f, 0.12f, 0.45f, 1.0f }, // GCodeExtrusionRole::SolidInfillNonplanar
|
||||
{ 0.94f, 0.25f, 0.25f, 1.0f }, // GCodeExtrusionRole::TopSolidInfill
|
||||
{ 0.32f, 0.12f, 0.45f, 1.0f }, // GCodeExtrusionRole::TopSolidInfillNonplanar
|
||||
{ 1.00f, 0.55f, 0.41f, 1.0f }, // GCodeExtrusionRole::Ironing
|
||||
{ 0.30f, 0.50f, 0.73f, 1.0f }, // GCodeExtrusionRole::BridgeInfill
|
||||
{ 1.00f, 1.00f, 1.00f, 1.0f }, // GCodeExtrusionRole::GapFill
|
||||
|
@ -1459,6 +1459,12 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("fuzzy_skin_thickness", category_path + "fuzzy-skin-thickness");
|
||||
optgroup->append_single_option_line("fuzzy_skin_point_dist", category_path + "fuzzy-skin-point-distance");
|
||||
|
||||
optgroup = page->new_optgroup(L("Nonplanar layers (experimental)"));
|
||||
category_path = "nonplanar-layers_19282821/#";
|
||||
optgroup->append_single_option_line("use_nonplanar_layers", category_path + "use-nonplanar-layers");
|
||||
optgroup->append_single_option_line("nonplanar_layers_angle", category_path + "nonplanar-layers-height");
|
||||
optgroup->append_single_option_line("nonplanar_layers_height", category_path + "nonplanar-layers-height");
|
||||
|
||||
page = add_options_page(L("Infill"), "infill");
|
||||
category_path = "infill_42#";
|
||||
optgroup = page->new_optgroup(L("Infill"));
|
||||
|
BIN
tests/data/nonplanar/wave_cube.stl
Normal file
BIN
tests/data/nonplanar/wave_cube.stl
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user