Slic3r/xs/src/libslic3r/SupportMaterial.hpp
2018-07-11 00:08:25 +02:00

443 lines
17 KiB
C++

#ifndef slic3r_SupportMaterial_hpp_
#define slic3r_SupportMaterial_hpp_
#include <vector>
#include <iostream>
#include <algorithm>
#include "libslic3r.h"
#include "PrintConfig.hpp"
#include "Flow.hpp"
#include "Layer.hpp"
#include "Geometry.hpp"
#include "Print.hpp"
#include "ClipperUtils.hpp"
#include "SupportMaterial.hpp"
#include "ExPolygon.hpp"
// Supports Material tests.
using namespace std;
namespace Slic3r
{
// how much we extend support around the actual contact area
constexpr coordf_t SUPPORT_MATERIAL_MARGIN = 1.5;
constexpr coordf_t PILLAR_SIZE = 2.5;
constexpr coordf_t PILLAR_SPACING = 10;
class SupportMaterial
{
public:
PrintConfig *config; ///<
PrintObjectConfig *object_config; ///<
Flow *flow; ///<
Flow *first_layer_flow; ///<
Flow *interface_flow; ///<
SupportMaterial(PrintConfig *print_config,
PrintObjectConfig *print_object_config,
Flow *flow = nullptr,
Flow *first_layer_flow = nullptr,
Flow *interface_flow = nullptr)
: config(print_config),
object_config(print_object_config),
flow(flow),
first_layer_flow(first_layer_flow),
interface_flow(interface_flow)
{}
void generate()
{}
void contact_area(PrintObject *object)
{
PrintObjectConfig conf = this->object_config;
// If user specified a custom angle threshold, convert it to radians.
float threshold_rad;
cout << conf.support_material_threshold << endl;
if (conf.support_material_threshold > 0) {
threshold_rad = Geometry::deg2rad(conf.support_material_threshold.value + 1.0);
// TODO @Samir55 add debug statetments.
}
// Build support on a build plate only? If so, then collect top surfaces into $buildplate_only_top_surfaces
// and subtract $buildplate_only_top_surfaces from the contact surfaces, so
// there is no contact surface supported by a top surface.
bool buildplate_only = (conf.support_material || conf.support_material_enforce_layers)
&& conf.support_material_buildplate_only;
SurfacesPtr buildplate_only_top_surfaces = SurfacesPtr();
Polygons contact;
Polygons overhangs; // This stores the actual overhang supported by each contact layer
for (int layer_id = 0; layer_id < object->layers.size(); layer_id++) {
// Note $layer_id might != $layer->id when raft_layers > 0
// so $layer_id == 0 means first object layer
// and $layer->id == 0 means first print layer (including raft).
// If no raft, and we're at layer 0, skip to layer 1
if (conf.raft_layers == 0 && layer_id == 0)
continue;
// With or without raft, if we're above layer 1, we need to quit
// support generation if supports are disabled, or if we're at a high
// enough layer that enforce-supports no longer applies.
if (layer_id > 0 && !conf.support_material && (layer_id >= conf.support_material_enforce_layers))
// If we are only going to generate raft just check
// the 'overhangs' of the first object layer.
break;
auto layer = object->get_layer(layer_id);
if (buildplate_only) {
// Collect the top surfaces up to this layer and merge them.
SurfacesPtr projection_new;
for (auto const &region : layer->regions) {
SurfacesPtr top_surfaces = region->slices.filter_by_type(stTop);
for (auto top_surface : top_surfaces) {
projection_new.push_back(top_surface);
}
}
if (!projection_new.empty()) {
// Merge the new top surfaces with the preceding top surfaces.
// Apply the safety offset to the newly added polygons, so they will connect
// with the polygons collected before,
// but don't apply the safety offset during the union operation as it would
// inflate the polygons over and over.
for (auto surface : projection_new) {
// buildplate_only_top_surfaces.push_back(offset(surface, scale_(0.01)));
}
}
}
// Detect overhangs and contact areas needed to support them.
if (layer_id == 0) {
// this is the first object layer, so we're here just to get the object
// footprint for the raft.
// we only consider contours and discard holes to get a more continuous raft.
for (auto const &contour : layer->slices.contours()) {
auto contour_clone = contour;
overhangs.push_back(contour_clone);
// contact.push_back(offset(overhangs.back(), static_cast<const float>(SUPPORT_MATERIAL_MARGIN)));
}
}
else {
Layer *lower_layer = object->get_layer(layer_id - 1);
for (auto layerm : layer->regions) {
float fw = layerm->flow(frExternalPerimeter).scaled_width();
Polygons difference;
// If a threshold angle was specified, use a different logic for detecting overhangs.
if ((conf.support_material && threshold_rad > 0)
|| layer_id <= conf.support_material_enforce_layers
|| (conf.raft_layers > 0 && layer_id == 0)) {
float d = 0;
float layer_threshold_rad = threshold_rad;
if (layer_id <= conf.support_material_enforce_layers) {
// Use ~45 deg number for enforced supports if we are in auto.
layer_threshold_rad = Geometry::deg2rad(89);
}
if (layer_threshold_rad > 0) {
d = scale_(lower_layer->height) *
((cos(layer_threshold_rad)) / (sin(layer_threshold_rad)));
}
difference = diff(offset(layerm->slices, -d), lower_layer->slices);
// only enforce spacing from the object ($fw/2) if the threshold angle
// is not too high: in that case, $d will be very small (as we need to catch
// very short overhangs), and such contact area would be eaten by the
// enforced spacing, resulting in high threshold angles to be almost ignored
if (d > fw / 2) {
difference = diff(offset(difference, d - fw / 2), lower_layer->slices);
}
}
else {
// $diff now contains the ring or stripe comprised between the boundary of
// lower slices and the centerline of the last perimeter in this overhanging layer.
// Void $diff means that there's no upper perimeter whose centerline is
// outside the lower slice boundary, thus no overhang
}
}
}
}
}
ExPolygons *object_top(PrintObject *object, SurfacesPtr contact)
{
// find object top surfaces
// we'll use them to clip our support and detect where does it stick.
ExPolygons *top = new ExPolygons();
if (object_config->support_material_buildplate_only.value)
return top;
}
void generate_interface_layers()
{}
void generate_bottom_interface_layers()
{}
void generate_base_layers()
{}
void generate_toolpaths()
{}
void generate_pillars_shape()
{}
// This method removes object silhouette from support material
// (it's used with interface and base only). It removes a bit more,
// leaving a thin gap between object and support in the XY plane.
void clip_with_object(map<int, Polygons> &support, vector<coordf_t> support_z, PrintObject &object)
{
int i = 0;
for (auto support_layer: support) {
if (support_layer.second.empty()) {
i++;
continue;
}
coordf_t z_max = support_z[i];
coordf_t z_min = (i == 0) ? 0 : support_z[i - 1];
LayerPtrs layers;
for (auto layer : object.layers) {
if (layer->print_z > z_min && (layer->print_z - layer->height) < z_max) {
layers.push_back(layer);
}
}
// $layer->slices contains the full shape of layer, thus including
// perimeter's width. $support contains the full shape of support
// material, thus including the width of its foremost extrusion.
// We leave a gap equal to a full extrusion width. TODO ask about this line @samir
Polygons slices;
for (Layer *l : layers) {
for (auto s : l->slices.contours()) {
slices.push_back(s);
}
}
support_layer.second = diff(support_layer.second, offset(slices, flow->scaled_width()));
}
/*
$support->{$i} = diff(
$support->{$i},
offset([ map @$_, map @{$_->slices}, @layers ], +$self->flow->scaled_width),
);
*/
}
void clip_with_shape(map<int, Polygons> &support, map<int, Polygons> &shape)
{
for (auto layer : support) {
// Don't clip bottom layer with shape so that we
// can generate a continuous base flange
// also don't clip raft layers
if (layer.first == 0) continue;
else if (layer.first < object_config->raft_layers) continue;
layer.second = intersection(layer.second, shape[layer.first]);
}
}
/// This method returns the indices of the layers overlapping with the given one.
vector<int> overlapping_layers(int layer_idx, vector<float> support_z)
{
vector<int> ret;
float z_max = support_z[layer_idx];
float z_min = layer_idx == 0 ? 0 : support_z[layer_idx - 1];
for (int i = 0; i < support_z.size(); i++) {
if (i == layer_idx) continue;
float z_max2 = support_z[i];
float z_min2 = i == 0 ? 0 : support_z[i - 1];
if (z_max > z_min2 && z_min < z_max2)
ret.push_back(i);
}
return ret;
}
vector<coordf_t> support_layers_z(vector<float> contact_z, vector<float> top_z, coordf_t max_object_layer_height)
{
// Quick table to check whether a given Z is a top surface.
map<float, bool> is_top;
for (auto z : top_z) is_top[z] = true;
// determine layer height for any non-contact layer
// we use max() to prevent many ultra-thin layers to be inserted in case
// layer_height > nozzle_diameter * 0.75.
auto nozzle_diameter = config->nozzle_diameter.get_at(object_config->support_material_extruder - 1);
auto support_material_height = (max_object_layer_height, (nozzle_diameter * 0.75));
coordf_t _contact_distance = this->contact_distance(support_material_height, nozzle_diameter);
// Initialize known, fixed, support layers.
vector<coordf_t> z;
for (auto c_z : contact_z) z.push_back(c_z);
for (auto t_z : top_z) {
z.push_back(t_z);
z.push_back(t_z + _contact_distance);
}
sort(z.begin(), z.end());
// Enforce first layer height.
coordf_t first_layer_height = object_config->first_layer_height;
while (!z.empty() && z.front() <= first_layer_height) z.erase(z.begin());
z.insert(z.begin(), first_layer_height);
// Add raft layers by dividing the space between first layer and
// first contact layer evenly.
if (object_config->raft_layers > 1 && z.size() >= 2) {
// z[1] is last raft layer (contact layer for the first layer object) TODO @Samir55 How so?
coordf_t height = (z[1] - z[0]) / (object_config->raft_layers - 1);
// since we already have two raft layers ($z[0] and $z[1]) we need to insert
// raft_layers-2 more
int idx = 1;
for (int j = 0; j < object_config->raft_layers - 2; j++) {
float z_new =
roundf(static_cast<float>((z[0] + height * idx) * 100)) / 100; // round it to 2 decimal places.
z.insert(z.begin() + idx, z_new);
idx++;
}
}
// Create other layers (skip raft layers as they're already done and use thicker layers).
for (size_t i = z.size(); i >= object_config->raft_layers; i--) {
coordf_t target_height = support_material_height;
if (i > 0 && is_top[z[i - 1]]) {
target_height = nozzle_diameter;
}
// Enforce first layer height.
if ((i == 0 && z[i] > target_height + first_layer_height)
|| (z[i] - z[i - 1] > target_height + EPSILON)) {
z.insert(z.begin() + i, (z[i] - target_height));
i++;
}
}
// Remove duplicates and make sure all 0.x values have the leading 0.
{
set<coordf_t> s;
for (auto el : z)
s.insert(roundf(static_cast<float>((el * 100)) / 100)); // round it to 2 decimal places.
z = vector<coordf_t>();
for (auto el : s)
z.push_back(el);
}
return z;
}
coordf_t contact_distance(coordf_t layer_height, coordf_t nozzle_diameter)
{
coordf_t extra = static_cast<float>(object_config->support_material_contact_distance.value);
if (extra == 0) {
return layer_height;
}
else {
return nozzle_diameter + extra;
}
}
};
class SupportMaterialTests
{
public:
bool test_1()
{
// Create a mesh & modelObject.
TriangleMesh mesh = TriangleMesh::make_cube(20, 20, 20);
// Create modelObject.
Model model = Model();
ModelObject *object = model.add_object();
object->add_volume(mesh);
model.add_default_instances();
// Align to origin.
model.align_instances_to_origin();
// Create Print.
Print print = Print();
// Configure the printObjectConfig.
print.default_object_config.set_deserialize("support_material", "1");
print.default_object_config.set_deserialize("layer_height", "0.2");
print.config.set_deserialize("first_layer_height", "0.3");
vector<float> contact_z;
vector<float> top_z;
contact_z.push_back(1.9);
top_z.push_back(1.9);
// Add the modelObject.
print.add_model_object(model.objects[0]);
// Create new supports.
SupportMaterial support = SupportMaterial(&print.config, &print.objects.front()->config);
vector<coordf_t>
support_z = support.support_layers_z(contact_z, top_z, print.default_object_config.layer_height);
bool
is_1 = (support_z[0] == print.default_object_config.first_layer_height); // 'first layer height is honored'.
bool is_2 = false; // 'no null or negative support layers'.
for (int i = 1; i < support_z.size(); ++i) {
if (support_z[i] - support_z[i - 1] <= 0) is_2 = true;
}
bool is_3 = false; // 'no layers thicker than nozzle diameter'.
for (int i = 1; i < support_z.size(); ++i) {
if (support_z[i] - support_z[i - 1] > print.config.nozzle_diameter.get_at(0) + EPSILON) is_2 = true;
}
coordf_t expected_top_spacing =
support.contact_distance(print.default_object_config.layer_height, print.config.nozzle_diameter.get_at(0));
coordf_t wrong_top_spacing = 0;
for (auto top_z_el : top_z) {
// find layer index of this top surface.
int layer_id = -1;
for (int i = 0; i < support_z.size(); i++) {
if (abs(support_z[i] - top_z_el) < EPSILON) {
layer_id = i;
i = static_cast<int>(support_z.size());
}
}
// check that first support layer above this top surface (or the next one) is spaced with nozzle diameter
if ((support_z[layer_id + 1] - support_z[layer_id]) != expected_top_spacing
&& (support_z[layer_id + 2] - support_z[layer_id]) != expected_top_spacing)
wrong_top_spacing = 1;
}
bool is_4 = !wrong_top_spacing; // 'layers above top surfaces are spaced correctly'
/* Test Also with this
$config->set('first_layer_height', 0.4);
$test->();
$config->set('layer_height', $config->nozzle_diameter->[0]);
$test->();
*/
}
};
}
#endif