2016-10-13 14:00:22 +00:00
|
|
|
|
#include "ClipperUtils.hpp"
|
|
|
|
|
#include "ExtrusionEntityCollection.hpp"
|
2016-10-16 14:30:56 +00:00
|
|
|
|
#include "PerimeterGenerator.hpp"
|
|
|
|
|
#include "Print.hpp"
|
|
|
|
|
#include "Layer.hpp"
|
|
|
|
|
#include "SupportMaterial.hpp"
|
|
|
|
|
#include "Fill/FillBase.hpp"
|
|
|
|
|
|
2016-10-13 14:00:22 +00:00
|
|
|
|
#include <cmath>
|
|
|
|
|
#include <cassert>
|
2016-10-17 07:27:20 +00:00
|
|
|
|
#include <memory>
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
namespace Slic3r {
|
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// Increment used to reach MARGIN in steps to avoid trespassing thin objects
|
|
|
|
|
#define NUM_MARGIN_STEPS 3
|
|
|
|
|
|
|
|
|
|
// Dimensions of a tree-like structure to save material
|
2016-10-13 14:00:22 +00:00
|
|
|
|
#define PILLAR_SIZE (2.5)
|
|
|
|
|
#define PILLAR_SPACING 10
|
|
|
|
|
|
2016-10-20 11:04:23 +00:00
|
|
|
|
PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object) :
|
|
|
|
|
m_object (object),
|
|
|
|
|
m_print_config (&object->print()->config),
|
|
|
|
|
m_object_config (&object->config),
|
|
|
|
|
|
|
|
|
|
m_first_layer_flow (0, 0, 0, false), // First layer flow will be set in the constructor code.
|
|
|
|
|
m_support_material_flow (Flow::new_from_config_width(
|
|
|
|
|
frSupportMaterial,
|
|
|
|
|
object->config.support_material_extrusion_width, // object->config.extrusion_width.value
|
|
|
|
|
object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1),
|
|
|
|
|
object->config.layer_height.value,
|
|
|
|
|
false)),
|
|
|
|
|
m_support_material_interface_flow(Flow::new_from_config_width(
|
|
|
|
|
frSupportMaterialInterface,
|
|
|
|
|
object->config.support_material_extrusion_width, // object->config.extrusion_width.value
|
|
|
|
|
object->print()->config.nozzle_diameter.get_at(object->config.support_material_interface_extruder-1),
|
|
|
|
|
object->config.layer_height.value,
|
|
|
|
|
false)),
|
|
|
|
|
m_soluble_interface (object->config.support_material_contact_distance.value == 0),
|
|
|
|
|
|
|
|
|
|
m_support_material_raft_base_flow(0, 0, 0, false),
|
|
|
|
|
m_support_material_raft_interface_flow(0, 0, 0, false),
|
|
|
|
|
m_support_material_raft_contact_flow(0, 0, 0, false),
|
|
|
|
|
|
|
|
|
|
m_has_raft (object->config.raft_layers.value > 0),
|
|
|
|
|
m_num_base_raft_layers (0),
|
|
|
|
|
m_num_interface_raft_layers (0),
|
|
|
|
|
m_num_contact_raft_layers (0),
|
|
|
|
|
|
|
|
|
|
// If set, the raft contact layer is laid with round strings, which are easily detachable
|
|
|
|
|
// from both the below and above layes.
|
|
|
|
|
// Otherwise a normal flow is used and the strings are squashed against the layer below,
|
|
|
|
|
// creating a firm bond with the layer below and making the interface top surface flat.
|
|
|
|
|
#if 1
|
|
|
|
|
// This is the standard Slic3r behavior.
|
|
|
|
|
m_raft_contact_layer_bridging(false),
|
|
|
|
|
m_object_1st_layer_bridging (true),
|
|
|
|
|
#else
|
|
|
|
|
// This is more akin to what Simplify3D or Zortrax do.
|
|
|
|
|
m_raft_contact_layer_bridging(true),
|
|
|
|
|
m_object_1st_layer_bridging (false),
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
m_raft_height (0.),
|
|
|
|
|
m_raft_base_height (0.),
|
|
|
|
|
m_raft_interface_height (0.),
|
|
|
|
|
m_raft_contact_height (0.),
|
|
|
|
|
|
|
|
|
|
// 50 mirons layer
|
|
|
|
|
m_support_layer_height_min (0.05),
|
|
|
|
|
m_support_layer_height_max (0.),
|
|
|
|
|
m_support_interface_layer_height_max(0.),
|
|
|
|
|
|
|
|
|
|
m_gap_extra_above (0.2),
|
|
|
|
|
m_gap_extra_below (0.2),
|
|
|
|
|
m_gap_xy (0.2),
|
|
|
|
|
|
|
|
|
|
// If enabled, the support layers will be synchronized with object layers.
|
|
|
|
|
// This does not prevent the support layers to be combined.
|
|
|
|
|
m_synchronize_support_layers_with_object(false),
|
|
|
|
|
// If disabled and m_synchronize_support_layers_with_object,
|
|
|
|
|
// the support layers will be synchronized with the object layers exactly, no layer will be combined.
|
|
|
|
|
m_combine_support_layers (true)
|
|
|
|
|
{
|
|
|
|
|
// Based on the raft style and size, initialize the raft layers and the 1st object layer attributes.
|
|
|
|
|
|
|
|
|
|
size_t num_raft_layers = m_object_config->raft_layers.value;
|
|
|
|
|
|
|
|
|
|
//FIXME better to draw thin strings, which are easier to remove from the object.
|
|
|
|
|
if (m_has_raft)
|
|
|
|
|
{
|
|
|
|
|
if (m_raft_contact_layer_bridging)
|
|
|
|
|
m_support_material_raft_contact_flow = Flow::new_from_spacing(
|
|
|
|
|
m_support_material_raft_interface_flow.spacing(),
|
|
|
|
|
m_support_material_raft_interface_flow.nozzle_diameter,
|
|
|
|
|
m_support_material_raft_interface_flow.height,
|
|
|
|
|
true);
|
|
|
|
|
|
|
|
|
|
if (m_raft_contact_layer_bridging && num_raft_layers == 1)
|
|
|
|
|
// The bridging contact layer will not bond to the bed well on its own.
|
|
|
|
|
// Ensure there is at least the 1st layer printed with a firm squash.
|
|
|
|
|
++ num_raft_layers;
|
|
|
|
|
|
|
|
|
|
// Split the raft layers into a single contact layer
|
|
|
|
|
// and an equal number of interface and base layers,
|
|
|
|
|
// with m_num_interface_raft_layers >= m_num_base_raft_layers.
|
|
|
|
|
m_num_contact_raft_layers = 1;
|
|
|
|
|
m_num_interface_raft_layers = num_raft_layers / 2;
|
|
|
|
|
m_num_base_raft_layers = num_raft_layers - m_num_contact_raft_layers - m_num_interface_raft_layers;
|
|
|
|
|
assert(m_num_interface_raft_layers >= m_num_base_raft_layers);
|
|
|
|
|
assert(m_num_contact_raft_layers + m_num_base_raft_layers + m_num_interface_raft_layers == num_raft_layers);
|
|
|
|
|
|
|
|
|
|
m_raft_contact_height = m_num_contact_raft_layers * m_support_material_raft_contact_flow.height;
|
|
|
|
|
if (m_num_base_raft_layers > 0) {
|
|
|
|
|
m_raft_base_height = first_layer_height() + (m_num_base_raft_layers - 1) * m_support_material_raft_base_flow.height;
|
|
|
|
|
m_raft_interface_height = m_num_interface_raft_layers * m_support_material_raft_interface_flow.height;
|
|
|
|
|
} else if (m_num_interface_raft_layers > 0) {
|
|
|
|
|
m_raft_base_height = 0;
|
|
|
|
|
m_raft_interface_height = first_layer_height() + (m_num_interface_raft_layers - 1) * m_support_material_raft_interface_flow.height;
|
|
|
|
|
} else {
|
|
|
|
|
m_raft_base_height = 0;
|
|
|
|
|
m_raft_interface_height = 0;
|
|
|
|
|
}
|
|
|
|
|
m_raft_height = m_raft_base_height + m_raft_interface_height + m_raft_contact_height;
|
|
|
|
|
|
|
|
|
|
// Find the layer height of the 1st object layer.
|
|
|
|
|
if (m_object_1st_layer_bridging) {
|
|
|
|
|
// Use an average nozzle diameter.
|
|
|
|
|
std::set<size_t> extruders = m_object->print()->object_extruders();
|
|
|
|
|
coordf_t nozzle_dmr = 0;
|
|
|
|
|
for (std::set<size_t>::const_iterator it = extruders.begin(); it != extruders.end(); ++ it) {
|
|
|
|
|
nozzle_dmr += m_object->print()->config.nozzle_diameter.get_at(*it);
|
|
|
|
|
}
|
|
|
|
|
nozzle_dmr /= extruders.size();
|
|
|
|
|
m_object_1st_layer_height = nozzle_dmr;
|
|
|
|
|
} else {
|
|
|
|
|
m_object_1st_layer_height = m_object->config.layer_height.value;
|
|
|
|
|
for (t_layer_height_ranges::const_iterator it = m_object->layer_height_ranges.begin(); it != m_object->layer_height_ranges.end(); ++ it) {
|
|
|
|
|
if (m_object_1st_layer_height >= it->first.first && m_object_1st_layer_height <= it->first.second) {
|
|
|
|
|
m_object_1st_layer_height = it->second;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_object_1st_layer_gap = m_soluble_interface ? 0 : m_object_config->support_material_contact_distance;
|
|
|
|
|
m_object_1st_layer_print_z = m_raft_height + m_object_1st_layer_gap + m_object_1st_layer_height;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// No raft.
|
|
|
|
|
m_raft_contact_layer_bridging = false;
|
|
|
|
|
m_object_1st_layer_bridging = false;
|
|
|
|
|
m_object_1st_layer_height = m_first_layer_flow.height;
|
|
|
|
|
m_object_1st_layer_gap = 0;
|
|
|
|
|
m_object_1st_layer_print_z = m_object_1st_layer_height;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// Using the std::deque as an allocator.
|
2016-10-20 11:04:23 +00:00
|
|
|
|
inline PrintObjectSupportMaterial::MyLayer& layer_allocate(
|
|
|
|
|
std::deque<PrintObjectSupportMaterial::MyLayer> &layer_storage,
|
|
|
|
|
PrintObjectSupportMaterial::SupporLayerType layer_type)
|
2016-10-13 14:00:22 +00:00
|
|
|
|
{
|
2016-10-20 11:04:23 +00:00
|
|
|
|
layer_storage.push_back(PrintObjectSupportMaterial::MyLayer());
|
2016-10-16 14:30:56 +00:00
|
|
|
|
layer_storage.back().layer_type = layer_type;
|
|
|
|
|
return layer_storage.back();
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-20 11:04:23 +00:00
|
|
|
|
inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const PrintObjectSupportMaterial::MyLayersPtr &src)
|
2016-10-16 14:30:56 +00:00
|
|
|
|
{
|
|
|
|
|
dst.insert(dst.end(), src.begin(), src.end());
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
inline void polygons_append(Polygons &dst, const Polygons &src)
|
|
|
|
|
{
|
|
|
|
|
dst.insert(dst.end(), src.begin(), src.end());
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-20 11:04:23 +00:00
|
|
|
|
void PrintObjectSupportMaterial::generate(PrintObject &object)
|
2016-10-13 14:00:22 +00:00
|
|
|
|
{
|
|
|
|
|
coordf_t max_object_layer_height = 0.;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
for (size_t i = 0; i < object.layer_count(); ++ i)
|
|
|
|
|
max_object_layer_height = std::max(max_object_layer_height, object.get_layer(i)->height);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
if (m_support_layer_height_max == 0)
|
2016-10-20 11:04:23 +00:00
|
|
|
|
m_support_layer_height_max = std::max(max_object_layer_height, 0.75 * m_support_material_flow.nozzle_diameter);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
if (m_support_interface_layer_height_max == 0)
|
2016-10-20 11:04:23 +00:00
|
|
|
|
m_support_interface_layer_height_max = std::max(max_object_layer_height, 0.75 * m_support_material_interface_flow.nozzle_diameter);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
// Layer instances will be allocated by std::deque and they will be kept until the end of this function call.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// The layers will be referenced by various LayersPtr (of type std::vector<Layer*>)
|
|
|
|
|
MyLayerStorage layer_storage;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// Determine the top contact surfaces of the support, defined as:
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// contact = overhangs - clearance + margin
|
|
|
|
|
// This method is responsible for identifying what contact surfaces
|
|
|
|
|
// should the support material expose to the object in order to guarantee
|
|
|
|
|
// that it will be effective, regardless of how it's built below.
|
2016-10-20 11:04:23 +00:00
|
|
|
|
// If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette without holes.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
MyLayersPtr top_contacts = this->top_contact_layers(object, layer_storage);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
if (top_contacts.empty())
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// Nothing is supported, no supports are generated.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// Determine the bottom contact surfaces of the supports over the top surfaces of the object.
|
|
|
|
|
// Depending on whether the support is soluble or not, the contact layer thickness is decided.
|
|
|
|
|
MyLayersPtr bottom_contacts = this->bottom_contact_layers(object, top_contacts, layer_storage);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// Because the top and bottom contacts are thick slabs, they may overlap causing over extrusion
|
|
|
|
|
// and unwanted strong bonds to the object.
|
|
|
|
|
// Rather trim the top contacts by their overlapping bottom contacts to leave a gap instead of over extruding.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
this->trim_top_contacts_by_bottom_contacts(object, bottom_contacts, top_contacts);
|
|
|
|
|
|
2016-10-20 11:04:23 +00:00
|
|
|
|
// Generate empty intermediate layers between the top / bottom support contact layers,
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// The layers may or may not be synchronized with the object layers, depending on the configuration.
|
|
|
|
|
// For example, a single nozzle multi material printing will need to generate a waste tower, which in turn
|
|
|
|
|
// wastes less material, if there are as little layers as possible, therefore minimizing the material swaps.
|
|
|
|
|
MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers(
|
|
|
|
|
object, bottom_contacts, top_contacts, layer_storage, max_object_layer_height);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
2016-10-20 11:04:23 +00:00
|
|
|
|
// Fill in intermediate layers between the top / bottom support contact layers, trimmed by the object.
|
|
|
|
|
this->generate_base_layers(object, bottom_contacts, top_contacts, intermediate_layers);
|
|
|
|
|
|
|
|
|
|
// If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette without holes.
|
|
|
|
|
// Add the bottom contacts to the raft, inflate the support bases.
|
|
|
|
|
// There is a contact layer below the 1st object layer in the bottom contacts.
|
|
|
|
|
// There is also a 1st intermediate layer containing bases of support columns.
|
|
|
|
|
// Extend the bases of the support columns and create the raft base.
|
|
|
|
|
Polygons raft = this->generate_raft_base(object, bottom_contacts, intermediate_layers);
|
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
/*
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// If we wanted to apply some special logic to the first support layers lying on
|
|
|
|
|
// object's top surfaces this is the place to detect them
|
|
|
|
|
LayersSet shape;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
if (m_objectconfig->support_material_pattern.value == smpPillars)
|
2016-10-13 14:00:22 +00:00
|
|
|
|
shape = this->generate_pillars_shape(contact, support_z);
|
2016-10-16 14:30:56 +00:00
|
|
|
|
*/
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// Propagate top / bottom contact layers to generate interface layers.
|
|
|
|
|
MyLayersPtr interface_layers = this->generate_interface_layers(
|
|
|
|
|
object, bottom_contacts, top_contacts, intermediate_layers, layer_storage);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
/*
|
|
|
|
|
// Clip with the pillars.
|
|
|
|
|
if (! shape.empty()) {
|
|
|
|
|
this->clip_with_shape(interface, shape);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
this->clip_with_shape(base, shape);
|
2016-10-16 14:30:56 +00:00
|
|
|
|
}
|
|
|
|
|
*/
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// Install support layers into the object.
|
|
|
|
|
MyLayersPtr layers_sorted;
|
|
|
|
|
layers_sorted.reserve(bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size());
|
|
|
|
|
layers_append(layers_sorted, bottom_contacts);
|
|
|
|
|
layers_append(layers_sorted, top_contacts);
|
|
|
|
|
layers_append(layers_sorted, intermediate_layers);
|
|
|
|
|
layers_append(layers_sorted, interface_layers);
|
|
|
|
|
std::sort(layers_sorted.begin(), layers_sorted.end());
|
|
|
|
|
|
|
|
|
|
int layer_id = 0;
|
|
|
|
|
for (int i = 0; i < int(layers_sorted.size());) {
|
|
|
|
|
// Find the last layer with the same print_z, find the minimum layer height of all.
|
|
|
|
|
int j = i + 1;
|
|
|
|
|
coordf_t height_min = layers_sorted[i]->height;
|
|
|
|
|
for (; j < layers_sorted.size() && layers_sorted[i]->print_z == layers_sorted[j]->print_z; ++ j)
|
|
|
|
|
height_min = std::min(height_min, layers_sorted[j]->height);
|
|
|
|
|
object.add_support_layer(layer_id, height_min, layers_sorted[i]->print_z);
|
|
|
|
|
if (layer_id > 0) {
|
|
|
|
|
SupportLayer *sl1 = object.support_layers[object.support_layer_count()-2];
|
|
|
|
|
SupportLayer *sl2 = object.support_layers.back();
|
|
|
|
|
sl1->upper_layer = sl2;
|
|
|
|
|
sl2->lower_layer = sl1;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
2016-10-16 14:30:56 +00:00
|
|
|
|
i = j;
|
|
|
|
|
++ layer_id;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate the actual toolpaths and save them into each layer.
|
2016-10-20 11:04:23 +00:00
|
|
|
|
this->generate_toolpaths(object, raft, bottom_contacts, top_contacts, intermediate_layers, interface_layers);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void collect_region_slices_by_type(const Layer &layer, SurfaceType surface_type, Polygons &out)
|
|
|
|
|
{
|
|
|
|
|
// 1) Count the new polygons first.
|
|
|
|
|
size_t n_polygons_new = 0;
|
|
|
|
|
for (LayerRegionPtrs::const_iterator it_region = layer.regions.begin(); it_region != layer.regions.end(); ++ it_region) {
|
|
|
|
|
const LayerRegion ®ion = *(*it_region);
|
|
|
|
|
const SurfaceCollection &slices = region.slices;
|
|
|
|
|
for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) {
|
|
|
|
|
const Surface &surface = *it;
|
|
|
|
|
if (surface.surface_type == surface_type)
|
|
|
|
|
n_polygons_new += surface.expolygon.holes.size() + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2) Collect the new polygons.
|
|
|
|
|
out.reserve(out.size() + n_polygons_new);
|
|
|
|
|
for (LayerRegionPtrs::const_iterator it_region = layer.regions.begin(); it_region != layer.regions.end(); ++ it_region) {
|
|
|
|
|
const LayerRegion ®ion = *(*it_region);
|
|
|
|
|
const SurfaceCollection &slices = region.slices;
|
|
|
|
|
for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) {
|
|
|
|
|
const Surface &surface = *it;
|
|
|
|
|
if (surface.surface_type == surface_type) {
|
|
|
|
|
out.push_back(surface.expolygon.contour);
|
|
|
|
|
out.insert(out.end(), surface.expolygon.holes.begin(), surface.expolygon.holes.end());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_type)
|
|
|
|
|
{
|
|
|
|
|
Polygons out;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
collect_region_slices_by_type(layer, surface_type, out);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collect outer contours of all expolygons in all layer region slices.
|
|
|
|
|
void collect_region_slices_outer(const Layer &layer, Polygons &out)
|
|
|
|
|
{
|
|
|
|
|
// 1) Count the new polygons first.
|
|
|
|
|
size_t n_polygons_new = 0;
|
|
|
|
|
for (LayerRegionPtrs::const_iterator it_region = layer.regions.begin(); it_region != layer.regions.end(); ++ it_region) {
|
|
|
|
|
const LayerRegion ®ion = *(*it_region);
|
|
|
|
|
n_polygons_new += region.slices.surfaces.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2) Collect the new polygons.
|
|
|
|
|
out.reserve(out.size() + n_polygons_new);
|
|
|
|
|
for (LayerRegionPtrs::const_iterator it_region = layer.regions.begin(); it_region != layer.regions.end(); ++ it_region) {
|
|
|
|
|
const LayerRegion ®ion = *(*it_region);
|
|
|
|
|
for (Surfaces::const_iterator it = region.slices.surfaces.begin(); it != region.slices.surfaces.end(); ++ it)
|
|
|
|
|
out.push_back(it->expolygon.contour);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collect outer contours of all expolygons in all layer region slices.
|
|
|
|
|
Polygons collect_region_slices_outer(const Layer &layer)
|
|
|
|
|
{
|
|
|
|
|
Polygons out;
|
|
|
|
|
collect_region_slices_outer(layer, out);
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// Find the top contact surfaces of the support or the raft.
|
2016-10-20 11:04:23 +00:00
|
|
|
|
PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_layers(const PrintObject &object, MyLayerStorage &layer_storage) const
|
2016-10-13 14:00:22 +00:00
|
|
|
|
{
|
|
|
|
|
// Output layers, sorte by top Z.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
MyLayersPtr contact_out;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// If user specified a custom angle threshold, convert it to radians.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
double threshold_rad = 0.;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
if (m_object_config->support_material_threshold > 0) {
|
|
|
|
|
threshold_rad = M_PI * double(m_object_config->support_material_threshold + 1) / 180.; // +1 makes the threshold inclusive
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// Slic3r::debugf "Threshold angle = %d°\n", rad2deg($threshold_rad);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
bool buildplate_only = m_object_config->support_material && m_object_config->support_material_buildplate_only;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
Polygons buildplate_only_top_surfaces;
|
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// Determine top contact areas.
|
|
|
|
|
for (size_t layer_id = 0; layer_id < object.layer_count(); ++ layer_id) {
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// Note that layer_id < layer->id when raft_layers > 0 as the layer->id incorporates the raft layers.
|
|
|
|
|
// So layer_id == 0 means first object layer and layer->id == 0 means first print layer if there are no explicit raft layers.
|
2016-10-20 11:04:23 +00:00
|
|
|
|
if (this->has_raft()) {
|
|
|
|
|
if (! this->has_support() && layer_id > 0)
|
|
|
|
|
// If we are only going to generate raft. Just check for the 'overhangs' of the first object layer.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
break;
|
2016-10-20 11:04:23 +00:00
|
|
|
|
// Check for the overhangs at any object layer including the 1st layer.
|
|
|
|
|
} else if (layer_id == 0) {
|
|
|
|
|
// No raft, 1st object layer cannot be supported by a support contact layer as it sticks directly to print bed.
|
|
|
|
|
continue;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
const Layer &layer = *object.get_layer(layer_id);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
if (buildplate_only) {
|
|
|
|
|
// Collect the top surfaces up to this layer and merge them.
|
|
|
|
|
Polygons projection_new = collect_region_slices_by_type(layer, stTop);
|
|
|
|
|
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.
|
|
|
|
|
projection_new = offset(projection_new, scale_(0.01));
|
2016-10-20 11:04:23 +00:00
|
|
|
|
polygons_append(buildplate_only_top_surfaces, projection_new);
|
|
|
|
|
buildplate_only_top_surfaces = union_(buildplate_only_top_surfaces, false); // don't apply the safety offset.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// Detect overhangs and contact areas needed to support them.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
Polygons overhang_polygons;
|
|
|
|
|
Polygons contact_polygons;
|
|
|
|
|
if (layer_id == 0) {
|
2016-10-20 11:04:23 +00:00
|
|
|
|
// This is the first object layer, so the object is being printed on a raft and
|
|
|
|
|
// we're here just to get the object footprint for the raft.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// We only consider contours and discard holes to get a more continuous raft.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
overhang_polygons = collect_region_slices_outer(layer);
|
|
|
|
|
// Extend by SUPPORT_MATERIAL_MARGIN, which is 1.5mm
|
2016-10-16 14:30:56 +00:00
|
|
|
|
contact_polygons = offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN));
|
2016-10-13 14:00:22 +00:00
|
|
|
|
} else {
|
2016-10-20 11:04:23 +00:00
|
|
|
|
// Generate overhang / contact_polygons for non-raft layers.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
const Layer &lower_layer = *object.get_layer(int(layer_id)-1);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
for (LayerRegionPtrs::const_iterator it_layerm = layer.regions.begin(); it_layerm != layer.regions.end(); ++ it_layerm) {
|
|
|
|
|
const LayerRegion &layerm = *(*it_layerm);
|
|
|
|
|
// Extrusion width accounts for the roundings of the extrudates.
|
|
|
|
|
// It is the maximum widh of the extrudate.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
coord_t fw = layerm.flow(frExternalPerimeter).scaled_width();
|
|
|
|
|
coordf_t lower_layer_offset =
|
|
|
|
|
(layer_id < m_object_config->support_material_enforce_layers) ?
|
|
|
|
|
// Enforce a full possible support, ignore the overhang angle.
|
|
|
|
|
0 :
|
|
|
|
|
(threshold_rad > 0. ?
|
|
|
|
|
// Overhang defined by an angle.
|
|
|
|
|
scale_(lower_layer.height * cos(threshold_rad) / sin(threshold_rad)) :
|
|
|
|
|
// Overhang defined by half the extrusion width.
|
|
|
|
|
0.5 * fw);
|
2016-10-20 11:04:23 +00:00
|
|
|
|
// Overhang polygons for this layer and region.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
Polygons diff_polygons;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
if (lower_layer_offset == 0.) {
|
2016-10-13 14:00:22 +00:00
|
|
|
|
diff_polygons = diff(
|
2016-10-16 14:30:56 +00:00
|
|
|
|
(Polygons)layerm.slices,
|
|
|
|
|
(Polygons)lower_layer.slices);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
} else {
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// Get the regions needing a suport.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
diff_polygons = diff(
|
|
|
|
|
(Polygons)layerm.slices,
|
2016-10-16 14:30:56 +00:00
|
|
|
|
offset((Polygons)lower_layer.slices, lower_layer_offset));
|
|
|
|
|
// Collapse very tiny spots.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
diff_polygons = offset2(diff_polygons, -0.1*fw, +0.1*fw);
|
2016-10-16 14:30:56 +00:00
|
|
|
|
if (diff_polygons.empty())
|
|
|
|
|
continue;
|
|
|
|
|
// Offset the support regions back to a full overhang, restrict them to the full overhang.
|
|
|
|
|
diff_polygons = intersection(offset(diff_polygons, lower_layer_offset), (Polygons)layerm.slices);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
2016-10-16 14:30:56 +00:00
|
|
|
|
if (diff_polygons.empty())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (m_object_config->dont_support_bridges) {
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// compute the area of bridging perimeters
|
|
|
|
|
// Note: this is duplicate code from GCode.pm, we need to refactor
|
|
|
|
|
|
|
|
|
|
Polygons bridged_perimeters;
|
|
|
|
|
{
|
2016-10-16 14:30:56 +00:00
|
|
|
|
Flow bridge_flow = layerm.flow(frPerimeter, true);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
coordf_t nozzle_diameter = m_print_config->nozzle_diameter.get_at(
|
|
|
|
|
layerm.region()->config.perimeter_extruder-1);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
Polygons lower_grown_slices = offset((Polygons)lower_layer.slices, +scale_(0.5*nozzle_diameter));
|
|
|
|
|
|
|
|
|
|
// TODO: split_at_first_point() could split a bridge mid-way
|
|
|
|
|
Polylines overhang_perimeters;
|
|
|
|
|
for (size_t i = 0; i < layerm.perimeters.entities.size(); ++ i) {
|
|
|
|
|
ExtrusionEntity *entity = layerm.perimeters.entities[i];
|
2016-10-16 14:30:56 +00:00
|
|
|
|
ExtrusionLoop *loop = dynamic_cast<Slic3r::ExtrusionLoop*>(entity);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
overhang_perimeters.push_back(loop ?
|
2016-10-16 14:30:56 +00:00
|
|
|
|
loop->as_polyline() :
|
|
|
|
|
dynamic_cast<const Slic3r::ExtrusionPath*>(entity)->polyline);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline()
|
|
|
|
|
for (Polylines::iterator it = overhang_perimeters.begin(); it != overhang_perimeters.end(); ++ it)
|
2016-10-16 14:30:56 +00:00
|
|
|
|
it->points[0].x += 1;
|
|
|
|
|
diff(overhang_perimeters, lower_grown_slices, &overhang_perimeters);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
// only consider straight overhangs
|
|
|
|
|
// only consider overhangs having endpoints inside layer's slices
|
|
|
|
|
// convert bridging polylines into polygons by inflating them with their thickness
|
|
|
|
|
// since we're dealing with bridges, we can't assume width is larger than spacing,
|
|
|
|
|
// so we take the largest value and also apply safety offset to be ensure no gaps
|
|
|
|
|
// are left in between
|
2016-10-16 14:30:56 +00:00
|
|
|
|
coordf_t w = std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing());
|
2016-10-13 14:00:22 +00:00
|
|
|
|
for (Polylines::iterator it = overhang_perimeters.begin(); it != overhang_perimeters.end(); ++ it) {
|
|
|
|
|
if (it->is_straight()) {
|
2016-10-16 14:30:56 +00:00
|
|
|
|
it->extend_start(fw);
|
|
|
|
|
it->extend_end(fw);
|
|
|
|
|
if (layer.slices.contains(it->first_point()) && layer.slices.contains(it->last_point())) {
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// Offset a polyline into a polygon.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
Polylines tmp; tmp.push_back(*it);
|
|
|
|
|
Polygons out;
|
|
|
|
|
offset(tmp, &out, 0.5 * w + 10.);
|
|
|
|
|
polygons_append(bridged_perimeters, out);
|
|
|
|
|
}
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-10-16 14:30:56 +00:00
|
|
|
|
bridged_perimeters = union_(bridged_perimeters);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (1) {
|
|
|
|
|
// remove the entire bridges and only support the unsupported edges
|
|
|
|
|
Polygons bridges;
|
|
|
|
|
for (Surfaces::const_iterator it = layerm.fill_surfaces.surfaces.begin(); it != layerm.fill_surfaces.surfaces.end(); ++ it) {
|
|
|
|
|
if (it->surface_type == stBottomBridge && it->bridge_angle != -1) {
|
|
|
|
|
bridges.push_back(it->expolygon.contour);
|
|
|
|
|
bridges.insert(bridges.end(), it->expolygon.holes.begin(), it->expolygon.holes.end());
|
|
|
|
|
}
|
2016-10-16 14:30:56 +00:00
|
|
|
|
}
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
bridged_perimeters.insert(bridged_perimeters.end(), bridges.begin(), bridges.end());
|
|
|
|
|
diff_polygons = diff(diff_polygons, bridged_perimeters, true);
|
2016-10-16 14:30:56 +00:00
|
|
|
|
|
|
|
|
|
Polygons unsupported_bridge_polygons;
|
|
|
|
|
for (Polylines::const_iterator it = layerm.unsupported_bridge_edges.polylines.begin();
|
|
|
|
|
it != layerm.unsupported_bridge_edges.polylines.end(); ++ it) {
|
|
|
|
|
// Offset a polyline into a polygon.
|
|
|
|
|
Polylines tmp; tmp.push_back(*it);
|
|
|
|
|
Polygons out;
|
|
|
|
|
offset(tmp, &out, scale_(SUPPORT_MATERIAL_MARGIN));
|
|
|
|
|
polygons_append(unsupported_bridge_polygons, out);
|
|
|
|
|
}
|
|
|
|
|
Polygons bridge_anchors = intersection(unsupported_bridge_polygons, bridges);
|
|
|
|
|
polygons_append(diff_polygons, bridge_anchors);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
} else {
|
|
|
|
|
// just remove bridged areas
|
|
|
|
|
diff_polygons = diff(diff_polygons, layerm.bridged, true);
|
|
|
|
|
}
|
2016-10-16 14:30:56 +00:00
|
|
|
|
} // if (m_objconfig->dont_support_bridges)
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
if (buildplate_only) {
|
|
|
|
|
// Don't support overhangs above the top surfaces.
|
2016-10-20 11:04:23 +00:00
|
|
|
|
// This step is done before the contact surface is calculated by growing the overhang region.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
diff_polygons = diff(diff_polygons, buildplate_only_top_surfaces);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (diff_polygons.empty())
|
|
|
|
|
continue;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
|
|
|
|
|
polygons_append(overhang_polygons, diff_polygons);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
// Let's define the required contact area by using a max gap of half the upper
|
|
|
|
|
// extrusion width and extending the area according to the configured margin.
|
|
|
|
|
// We increment the area in steps because we don't want our support to overflow
|
|
|
|
|
// on the other side of the object (if it's very thin).
|
|
|
|
|
{
|
2016-10-20 11:04:23 +00:00
|
|
|
|
//FIMXE 1) Make the offset configurable, 2) Make the Z span configurable.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
Polygons slices_margin = offset((Polygons)lower_layer.slices, float(0.5*fw));
|
2016-10-13 14:00:22 +00:00
|
|
|
|
if (buildplate_only) {
|
|
|
|
|
// Trim the inflated contact surfaces by the top surfaces as well.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
polygons_append(slices_margin, buildplate_only_top_surfaces);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
slices_margin = union_(slices_margin);
|
|
|
|
|
}
|
2016-10-20 11:04:23 +00:00
|
|
|
|
// Offset the contact polygons outside.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) {
|
2016-10-13 14:00:22 +00:00
|
|
|
|
diff_polygons = diff(
|
|
|
|
|
offset(
|
2016-10-16 14:30:56 +00:00
|
|
|
|
diff_polygons,
|
|
|
|
|
SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS,
|
2016-10-13 14:00:22 +00:00
|
|
|
|
CLIPPER_OFFSET_SCALE,
|
|
|
|
|
ClipperLib::jtRound,
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// round mitter limit
|
2016-10-13 14:00:22 +00:00
|
|
|
|
scale_(0.05) * CLIPPER_OFFSET_SCALE),
|
|
|
|
|
slices_margin);
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-10-16 14:30:56 +00:00
|
|
|
|
polygons_append(contact_polygons, diff_polygons);
|
|
|
|
|
} // for each layer.region
|
|
|
|
|
} // end of Generate overhang/contact_polygons for non-raft layers.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
// now apply the contact areas to the layer were they need to be made
|
2016-10-16 14:30:56 +00:00
|
|
|
|
if (! contact_polygons.empty()) {
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// get the average nozzle diameter used on this layer
|
2016-10-16 14:30:56 +00:00
|
|
|
|
MyLayer &new_layer = layer_allocate(layer_storage, sltTopContact);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
new_layer.idx_object_layer_above = layer_id;
|
|
|
|
|
if (m_soluble_interface) {
|
|
|
|
|
// Align the contact surface height with a layer immediately below the supported layer.
|
|
|
|
|
new_layer.height = (layer_id > 0) ?
|
|
|
|
|
// Interface layer will be synchronized with the object.
|
|
|
|
|
object.get_layer(layer_id - 1)->height :
|
|
|
|
|
// Don't know the thickness of the raft layer yet.
|
|
|
|
|
0.;
|
2016-10-20 11:04:23 +00:00
|
|
|
|
new_layer.print_z = layer.print_z - layer.height;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
new_layer.bottom_z = new_layer.print_z - new_layer.height;
|
|
|
|
|
} else {
|
|
|
|
|
// Contact layer will be printed with a normal flow, but
|
|
|
|
|
// it will support layers printed with a bridging flow.
|
|
|
|
|
//FIXME Probably printing with the bridge flow? How about the unsupported perimeters? Are they printed with the bridging flow?
|
|
|
|
|
// In the future we may switch to a normal extrusion flow for the supported bridges.
|
|
|
|
|
// Get the average nozzle diameter used on this layer.
|
|
|
|
|
coordf_t nozzle_dmr = 0.;
|
|
|
|
|
size_t n_nozzle_dmrs = 0;
|
|
|
|
|
for (LayerRegionPtrs::const_iterator it_region_ptr = layer.regions.begin(); it_region_ptr != layer.regions.end(); ++ it_region_ptr) {
|
2016-10-16 14:30:56 +00:00
|
|
|
|
const PrintRegion ®ion = *(*it_region_ptr)->region();
|
|
|
|
|
nozzle_dmr += m_print_config->nozzle_diameter.get_at(region.config.perimeter_extruder-1);
|
|
|
|
|
nozzle_dmr += m_print_config->nozzle_diameter.get_at(region.config.infill_extruder-1);
|
|
|
|
|
nozzle_dmr += m_print_config->nozzle_diameter.get_at(region.config.solid_infill_extruder-1);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
n_nozzle_dmrs += 3;
|
|
|
|
|
}
|
|
|
|
|
nozzle_dmr /= n_nozzle_dmrs;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
new_layer.print_z = layer.print_z - nozzle_dmr - m_object_config->support_material_contact_distance;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and
|
|
|
|
|
// its height will be set adaptively later on.
|
|
|
|
|
new_layer.height = 0.;
|
|
|
|
|
new_layer.bottom_z = new_layer.print_z;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ignore this contact area if it's too low.
|
|
|
|
|
// Don't want to print a layer below the first layer height as it may not stick well.
|
|
|
|
|
//FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact
|
|
|
|
|
// and it may actually make sense to do it with a thinner layer than the first layer height.
|
2016-10-20 11:04:23 +00:00
|
|
|
|
if (new_layer.print_z < this->first_layer_height() + m_support_layer_height_min)
|
2016-10-13 14:00:22 +00:00
|
|
|
|
continue;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
|
2016-10-13 14:00:22 +00:00
|
|
|
|
new_layer.polygons.swap(contact_polygons);
|
2016-10-20 11:04:23 +00:00
|
|
|
|
// Store the overhang polygons as the aux_polygons.
|
|
|
|
|
// The overhang polygons are used in the path generator for planning of the contact circles.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
new_layer.aux_polygons = new Polygons();
|
|
|
|
|
new_layer.aux_polygons->swap(overhang_polygons);
|
2016-10-16 14:30:56 +00:00
|
|
|
|
contact_out.push_back(&new_layer);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
if (0) {
|
|
|
|
|
// Slic3r::SVG::output("out\\contact_" . $contact_z . ".svg",
|
|
|
|
|
// green_expolygons => union_ex($buildplate_only_top_surfaces),
|
|
|
|
|
// blue_expolygons => union_ex(\@contact),
|
|
|
|
|
// red_expolygons => union_ex(\@overhang),
|
|
|
|
|
// );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return contact_out;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-20 11:04:23 +00:00
|
|
|
|
PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_contact_layers(
|
2016-10-16 14:30:56 +00:00
|
|
|
|
const PrintObject &object, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage) const
|
2016-10-13 14:00:22 +00:00
|
|
|
|
{
|
|
|
|
|
// find object top surfaces
|
|
|
|
|
// we'll use them to clip our support and detect where does it stick
|
2016-10-16 14:30:56 +00:00
|
|
|
|
MyLayersPtr bottom_contacts;
|
|
|
|
|
if (! m_object_config->support_material_buildplate_only && ! top_contacts.empty())
|
2016-10-13 14:00:22 +00:00
|
|
|
|
{
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// Sum of unsupported contact areas above the current layer.print_z.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
Polygons projection;
|
|
|
|
|
// Last top contact layer visited when collecting the projection of contact areas.
|
|
|
|
|
int contact_idx = int(top_contacts.size()) - 1;
|
|
|
|
|
for (int layer_id = int(object.total_layer_count()) - 2; layer_id >= 0; -- layer_id) {
|
|
|
|
|
const Layer &layer = *object.get_layer(layer_id);
|
|
|
|
|
Polygons top = collect_region_slices_by_type(layer, stTop);
|
|
|
|
|
if (top.empty())
|
|
|
|
|
continue;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// Collect projections of all contact areas above or at the same level as this top surface.
|
|
|
|
|
for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z >= layer.print_z; -- contact_idx)
|
|
|
|
|
polygons_append(projection, top_contacts[contact_idx]->polygons);
|
|
|
|
|
// Now find whether any projection of the contact surfaces above layer.print_z not yet supported by any
|
|
|
|
|
// top surfaces above layer.print_z falls onto this top surface.
|
|
|
|
|
// touching are the contact surfaces supported exclusively by this top surfaaces.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
Polygons touching = intersection(projection, top);
|
|
|
|
|
if (touching.empty())
|
|
|
|
|
continue;
|
|
|
|
|
// Allocate a new bottom contact layer.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
MyLayer &layer_new = layer_allocate(layer_storage, sltBottomContact);
|
|
|
|
|
bottom_contacts.push_back(&layer_new);
|
|
|
|
|
// Grow top surfaces so that interface and support generation are generated
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// with some spacing from object - it looks we don't need the actual
|
|
|
|
|
// top shapes so this can be done here
|
2016-10-16 14:30:56 +00:00
|
|
|
|
layer_new.height = m_soluble_interface ?
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// Align the interface layer with the object's layer height.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
object.get_layer(layer_id + 1)->height :
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// Place a bridge flow interface layer over the top surface.
|
2016-10-20 11:04:23 +00:00
|
|
|
|
m_support_material_interface_flow.nozzle_diameter;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
layer_new.print_z = layer.print_z + layer_new.height +
|
2016-10-17 07:27:20 +00:00
|
|
|
|
(m_soluble_interface ? 0. : m_object_config->support_material_contact_distance.value);
|
2016-10-16 14:30:56 +00:00
|
|
|
|
layer_new.bottom_z = layer.print_z;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
layer_new.idx_object_layer_below = layer_id;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
layer_new.bridging = ! m_soluble_interface;
|
2016-10-20 11:04:23 +00:00
|
|
|
|
//FIXME how much to inflate the top surface?
|
|
|
|
|
Polygons poly_new = offset(touching, float(m_support_material_flow.scaled_width()));
|
2016-10-13 14:00:22 +00:00
|
|
|
|
layer_new.polygons.swap(poly_new);
|
|
|
|
|
// Remove the areas that touched from the projection that will continue on next, lower, top surfaces.
|
|
|
|
|
projection = diff(projection, touching);
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-10-16 14:30:56 +00:00
|
|
|
|
return bottom_contacts;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them.
|
2016-10-20 11:04:23 +00:00
|
|
|
|
void PrintObjectSupportMaterial::trim_top_contacts_by_bottom_contacts(
|
2016-10-16 14:30:56 +00:00
|
|
|
|
const PrintObject &object, const MyLayersPtr &bottom_contacts, MyLayersPtr &top_contacts) const
|
2016-10-13 14:00:22 +00:00
|
|
|
|
{
|
|
|
|
|
size_t idx_top_first = 0;
|
|
|
|
|
// For all bottom contact layers:
|
|
|
|
|
for (size_t idx_bottom = 0; idx_bottom < bottom_contacts.size() && idx_top_first < top_contacts.size(); ++ idx_bottom) {
|
2016-10-16 14:30:56 +00:00
|
|
|
|
const MyLayer &layer_bottom = *bottom_contacts[idx_bottom];
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// Find the first top layer overlapping with layer_bottom.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
while (idx_top_first < top_contacts.size() && top_contacts[idx_top_first]->print_z <= layer_bottom.print_z - layer_bottom.height)
|
2016-10-13 14:00:22 +00:00
|
|
|
|
++ idx_top_first;
|
|
|
|
|
// For all top contact layers overlapping with the thick bottom contact layer:
|
|
|
|
|
for (size_t idx_top = idx_top_first; idx_top < top_contacts.size(); ++ idx_top) {
|
2016-10-16 14:30:56 +00:00
|
|
|
|
MyLayer &layer_top = *top_contacts[idx_top];
|
2016-10-13 14:00:22 +00:00
|
|
|
|
coordf_t interface_z = m_soluble_interface ?
|
|
|
|
|
(layer_top.bottom_z + EPSILON) :
|
2016-10-20 11:04:23 +00:00
|
|
|
|
(layer_top.bottom_z - m_support_layer_height_min);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
if (interface_z < layer_bottom.print_z) {
|
|
|
|
|
// Layers overlap. Trim layer_top with layer_bottom.
|
|
|
|
|
layer_top.polygons = diff(layer_top.polygons, layer_bottom.polygons);
|
|
|
|
|
} else
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-20 11:04:23 +00:00
|
|
|
|
PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_support_layers(
|
2016-10-13 14:00:22 +00:00
|
|
|
|
const PrintObject &object,
|
2016-10-16 14:30:56 +00:00
|
|
|
|
const MyLayersPtr &bottom_contacts,
|
|
|
|
|
const MyLayersPtr &top_contacts,
|
|
|
|
|
MyLayerStorage &layer_storage,
|
|
|
|
|
const coordf_t max_object_layer_height) const
|
2016-10-13 14:00:22 +00:00
|
|
|
|
{
|
2016-10-20 11:04:23 +00:00
|
|
|
|
MyLayersPtr intermediate_layers;
|
|
|
|
|
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// Collect and sort the extremes (bottoms of the top contacts and tops of the bottom contacts).
|
|
|
|
|
std::vector<LayerExtreme> extremes;
|
|
|
|
|
extremes.reserve(top_contacts.size() + bottom_contacts.size());
|
|
|
|
|
for (size_t i = 0; i < top_contacts.size(); ++ i)
|
2016-10-20 11:04:23 +00:00
|
|
|
|
// Bottoms of the top contact layers. In case of non-soluble supports,
|
|
|
|
|
// the top contact layer thickness is not known yet.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
extremes.push_back(LayerExtreme(top_contacts[i], false));
|
|
|
|
|
for (size_t i = 0; i < bottom_contacts.size(); ++ i)
|
2016-10-20 11:04:23 +00:00
|
|
|
|
// Tops of the bottom contact layers.
|
2016-10-13 14:00:22 +00:00
|
|
|
|
extremes.push_back(LayerExtreme(bottom_contacts[i], true));
|
2016-10-20 11:04:23 +00:00
|
|
|
|
if (extremes.empty())
|
|
|
|
|
return intermediate_layers;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
std::sort(extremes.begin(), extremes.end());
|
|
|
|
|
|
2016-10-20 11:04:23 +00:00
|
|
|
|
// Top of the 0th layer.
|
|
|
|
|
coordf_t top_z_0th = this->raft_base_height() + this->raft_interface_height();
|
|
|
|
|
assert(extremes.front().z() > top_z_0th && extremes.front().z() >= this->first_layer_height());
|
|
|
|
|
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// Generate intermediate layers.
|
2016-10-20 11:04:23 +00:00
|
|
|
|
// The first intermediate layer is the same as the 1st layer if there is no raft,
|
|
|
|
|
// or the bottom of the first intermediate layer is aligned with the bottom of the raft contact layer.
|
|
|
|
|
// Intermediate layers are always printed with a normal etrusion flow (non-bridging).
|
|
|
|
|
for (size_t idx_extreme = 0; idx_extreme < extremes.size(); ++ idx_extreme) {
|
|
|
|
|
LayerExtreme *extr1 = (idx_extreme == 0) ? NULL : &extremes[idx_extreme-1];
|
|
|
|
|
coordf_t extr1z = (extr1 == NULL) ? top_z_0th : extr1->z();
|
|
|
|
|
LayerExtreme &extr2 = extremes[idx_extreme];
|
|
|
|
|
coordf_t extr2z = extr2.z();
|
|
|
|
|
coordf_t dist = extr2z - extr1z;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
assert(dist > 0.);
|
|
|
|
|
// Insert intermediate layers.
|
2016-10-20 11:04:23 +00:00
|
|
|
|
size_t n_layers_extra = size_t(ceil(dist / m_support_layer_height_max));
|
|
|
|
|
coordf_t step = dist / coordf_t(n_layers_extra);
|
2016-10-16 14:30:56 +00:00
|
|
|
|
if (! m_soluble_interface && extr2.layer->layer_type == sltTopContact) {
|
2016-10-20 11:04:23 +00:00
|
|
|
|
assert(extr2.layer->height == 0.);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// This is a top interface layer, which does not have a height assigned yet. Do it now.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
if (m_synchronize_support_layers_with_object) {
|
2016-10-20 11:04:23 +00:00
|
|
|
|
//FIXME
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// Find the
|
|
|
|
|
}
|
2016-10-13 14:00:22 +00:00
|
|
|
|
extr2.layer->height = step;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
extr2.layer->bottom_z = extr2.layer->print_z - step;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
-- n_layers_extra;
|
2016-10-20 11:04:23 +00:00
|
|
|
|
if (extr2.layer->bottom_z < this->first_layer_height()) {
|
|
|
|
|
// Split the span into two layers: the top layer up to the first layer height,
|
|
|
|
|
// and the new intermediate layer below.
|
|
|
|
|
// 1) Adjust the bottom of this top layer.
|
|
|
|
|
assert(n_layers_extra == 0);
|
|
|
|
|
extr2.layer->bottom_z = extr2z = this->first_layer_height();
|
|
|
|
|
extr2.layer->height = extr2.layer->print_z - extr2.layer->bottom_z;
|
|
|
|
|
// 2) Insert a new intermediate layer.
|
|
|
|
|
MyLayer &layer_new = layer_allocate(layer_storage, stlIntermediate);
|
|
|
|
|
layer_new.bottom_z = extr1z;
|
|
|
|
|
layer_new.print_z = this->first_layer_height();
|
|
|
|
|
layer_new.height = layer_new.print_z - layer_new.bottom_z;
|
|
|
|
|
intermediate_layers.push_back(&layer_new);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (n_layers_extra > 0 && extr1z + step < this->first_layer_height()) {
|
|
|
|
|
MyLayer &layer_new = layer_allocate(layer_storage, stlIntermediate);
|
|
|
|
|
layer_new.bottom_z = extr1z;
|
|
|
|
|
layer_new.print_z = extr1z = this->first_layer_height();
|
|
|
|
|
layer_new.height = layer_new.print_z - layer_new.bottom_z;
|
|
|
|
|
intermediate_layers.push_back(&layer_new);
|
|
|
|
|
dist = extr2z - extr1z;
|
|
|
|
|
assert(dist >= 0.);
|
|
|
|
|
n_layers_extra = size_t(ceil(dist / m_support_layer_height_max));
|
|
|
|
|
coordf_t step = dist / coordf_t(n_layers_extra);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
for (size_t i = 0; i < n_layers_extra; ++ i) {
|
2016-10-16 14:30:56 +00:00
|
|
|
|
MyLayer &layer_new = layer_allocate(layer_storage, stlIntermediate);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
layer_new.height = step;
|
2016-10-20 11:04:23 +00:00
|
|
|
|
layer_new.bottom_z = (i + 1 == n_layers_extra) ? extr2z : extr1z + i * step;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
layer_new.print_z = layer_new.bottom_z + step;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
intermediate_layers.push_back(&layer_new);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
return intermediate_layers;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// At this stage there shall be intermediate_layers allocated between bottom_contacts and top_contacts, but they have no polygons assigned.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// Also the bottom/top_contacts shall have a thickness assigned already.
|
2016-10-20 11:04:23 +00:00
|
|
|
|
void PrintObjectSupportMaterial::generate_base_layers(
|
2016-10-13 14:00:22 +00:00
|
|
|
|
const PrintObject &object,
|
2016-10-16 14:30:56 +00:00
|
|
|
|
const MyLayersPtr &bottom_contacts,
|
|
|
|
|
const MyLayersPtr &top_contacts,
|
|
|
|
|
MyLayersPtr &intermediate_layers) const
|
2016-10-13 14:00:22 +00:00
|
|
|
|
{
|
|
|
|
|
if (top_contacts.empty())
|
|
|
|
|
// No top contacts -> no intermediate layers will be produced.
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing);
|
2016-10-16 14:30:56 +00:00
|
|
|
|
//FIXME make configurable:
|
2016-10-20 11:04:23 +00:00
|
|
|
|
coordf_t overlap_extra_above = 0.01;
|
|
|
|
|
coordf_t overlap_extra_below = 0.01;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
int idx_top_contact_above = int(top_contacts.size()) - 1;
|
|
|
|
|
int idx_top_contact_overlapping = int(top_contacts.size()) - 1;
|
|
|
|
|
int idx_bottom_contact_overlapping = int(bottom_contacts.size()) - 1;
|
|
|
|
|
for (int idx_intermediate = int(intermediate_layers.size()) - 1; idx_intermediate >= 0; -- idx_intermediate)
|
|
|
|
|
{
|
2016-10-16 14:30:56 +00:00
|
|
|
|
MyLayer &layer_intermediate = *intermediate_layers[idx_intermediate];
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
// New polygons for layer_intermediate.
|
|
|
|
|
Polygons polygons_new;
|
|
|
|
|
|
|
|
|
|
// Find a top_contact layer touching the layer_intermediate from above, if any, and collect its polygons into polygons_new.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
while (idx_top_contact_above >= 0 && top_contacts[idx_top_contact_above]->bottom_z > layer_intermediate.print_z + EPSILON)
|
2016-10-13 14:00:22 +00:00
|
|
|
|
-- idx_top_contact_above;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
if (idx_top_contact_above >= 0 && top_contacts[idx_top_contact_above]->print_z > layer_intermediate.print_z)
|
|
|
|
|
polygons_append(polygons_new, top_contacts[idx_top_contact_above]->polygons);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
// Add polygons from the intermediate layer above.
|
|
|
|
|
if (idx_intermediate + 1 < int(intermediate_layers.size()))
|
2016-10-16 14:30:56 +00:00
|
|
|
|
polygons_append(polygons_new, intermediate_layers[idx_intermediate+1]->polygons);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
// Polygons to trim polygons_new.
|
|
|
|
|
Polygons polygons_trimming;
|
|
|
|
|
|
|
|
|
|
// Find the first top_contact layer intersecting with this layer.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
while (idx_top_contact_overlapping >= 0 &&
|
|
|
|
|
top_contacts[idx_top_contact_overlapping]->bottom_z > layer_intermediate.print_z + overlap_extra_above - EPSILON)
|
2016-10-13 14:00:22 +00:00
|
|
|
|
-- idx_top_contact_overlapping;
|
|
|
|
|
// Collect all the top_contact layer intersecting with this layer.
|
|
|
|
|
for (int i = idx_top_contact_overlapping; i >= 0; -- i) {
|
2016-10-16 14:30:56 +00:00
|
|
|
|
MyLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping];
|
2016-10-13 14:00:22 +00:00
|
|
|
|
if (layer_top_overlapping.print_z < layer_intermediate.bottom_z - overlap_extra_below)
|
|
|
|
|
break;
|
|
|
|
|
polygons_append(polygons_trimming, layer_top_overlapping.polygons);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find the first bottom_contact layer intersecting with this layer.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
while (idx_bottom_contact_overlapping >= 0 &&
|
|
|
|
|
bottom_contacts[idx_bottom_contact_overlapping]->bottom_z > layer_intermediate.print_z + overlap_extra_above - EPSILON)
|
2016-10-13 14:00:22 +00:00
|
|
|
|
-- idx_bottom_contact_overlapping;
|
|
|
|
|
// Collect all the top_contact layer intersecting with this layer.
|
|
|
|
|
for (int i = idx_bottom_contact_overlapping; i >= 0; -- i) {
|
2016-10-16 14:30:56 +00:00
|
|
|
|
MyLayer &layer_bottom_overlapping = *bottom_contacts[idx_bottom_contact_overlapping];
|
2016-10-13 14:00:22 +00:00
|
|
|
|
if (layer_bottom_overlapping.print_z < layer_intermediate.print_z - layer_intermediate.height - overlap_extra_below)
|
|
|
|
|
break;
|
|
|
|
|
polygons_append(polygons_trimming, layer_bottom_overlapping.polygons);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Trim the polygons, store them.
|
|
|
|
|
if (polygons_trimming.empty())
|
|
|
|
|
layer_intermediate.polygons.swap(polygons_new);
|
|
|
|
|
else
|
|
|
|
|
layer_intermediate.polygons = diff(
|
|
|
|
|
polygons_new,
|
|
|
|
|
polygons_trimming,
|
|
|
|
|
true); // safety offset to merge the touching source polygons
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
if (0) {
|
|
|
|
|
// Fillet the base polygons and trim them again with the top, interface and contact layers.
|
|
|
|
|
$base->{$i} = diff(
|
|
|
|
|
offset2(
|
|
|
|
|
$base->{$i},
|
|
|
|
|
$fillet_radius_scaled,
|
|
|
|
|
-$fillet_radius_scaled,
|
|
|
|
|
# Use a geometric offsetting for filleting.
|
|
|
|
|
CLIPPER_OFFSET_SCALE,
|
|
|
|
|
JT_ROUND,
|
|
|
|
|
0.2*$fillet_radius_scaled*CLIPPER_OFFSET_SCALE),
|
|
|
|
|
$trim_polygons,
|
|
|
|
|
false); // don't apply the safety offset.
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//FIXME This could be parallelized.
|
|
|
|
|
const coordf_t gap_extra_above = 0.1f;
|
|
|
|
|
const coordf_t gap_extra_below = 0.1f;
|
2016-10-20 11:04:23 +00:00
|
|
|
|
const coord_t gap_xy_scaled = m_support_material_flow.scaled_width();
|
2016-10-13 14:00:22 +00:00
|
|
|
|
size_t idx_object_layer_overlapping = 0;
|
|
|
|
|
// For all intermediate layers:
|
2016-10-16 14:30:56 +00:00
|
|
|
|
for (MyLayersPtr::iterator it_layer = intermediate_layers.begin(); it_layer != intermediate_layers.end(); ++ it_layer) {
|
|
|
|
|
MyLayer &layer_intermediate = *(*it_layer);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
if (layer_intermediate.polygons.empty())
|
|
|
|
|
continue;
|
|
|
|
|
// Find the overlapping object layers including the extra above / below gap.
|
|
|
|
|
while (idx_object_layer_overlapping < object.layer_count() &&
|
|
|
|
|
object.get_layer(idx_object_layer_overlapping)->print_z < layer_intermediate.print_z - layer_intermediate.height - gap_extra_below + EPSILON)
|
|
|
|
|
++ idx_object_layer_overlapping;
|
|
|
|
|
// Collect all the object layers intersecting with this layer.
|
|
|
|
|
Polygons polygons_trimming;
|
|
|
|
|
for (int i = idx_object_layer_overlapping; i < object.layer_count(); ++ i) {
|
2016-10-16 14:30:56 +00:00
|
|
|
|
const Layer &object_layer = *object.get_layer(i);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
if (object_layer.print_z > layer_intermediate.print_z + gap_extra_above - EPSILON)
|
|
|
|
|
break;
|
|
|
|
|
polygons_append(polygons_trimming, (Polygons)object_layer.slices);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// $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.
|
|
|
|
|
layer_intermediate.polygons = diff(
|
|
|
|
|
layer_intermediate.polygons,
|
2016-10-16 14:30:56 +00:00
|
|
|
|
offset(polygons_trimming, gap_xy_scaled));
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-20 11:04:23 +00:00
|
|
|
|
Polygons PrintObjectSupportMaterial::generate_raft_base(
|
|
|
|
|
const PrintObject &object,
|
|
|
|
|
const MyLayersPtr &bottom_contacts,
|
|
|
|
|
MyLayersPtr &intermediate_layers) const
|
|
|
|
|
{
|
|
|
|
|
assert(! bottom_contacts.empty());
|
|
|
|
|
MyLayer &contacts = *bottom_contacts.front();
|
|
|
|
|
MyLayer &columns_base = *intermediate_layers.front();
|
|
|
|
|
|
|
|
|
|
Polygons raft_polygons;
|
|
|
|
|
#if 0
|
|
|
|
|
const float inflate_factor = scale_(3.);
|
|
|
|
|
if (this->has_raft()) {
|
|
|
|
|
if (m_num_base_raft_layers == 0 && m_num_interface_raft_layers == 0 && m_num_contact_raft_layers == 1) {
|
|
|
|
|
// Having only the contact layer, which has the height of the 1st layer.
|
|
|
|
|
// We are free to merge the contacts with the columns_base, they will be printed the same way.
|
|
|
|
|
polygons_append(contacts.polygons, offset(columns_base.polygons, inflate_factor));
|
|
|
|
|
contacts.polygons = union_(contacts.polygons);
|
|
|
|
|
} else {
|
|
|
|
|
// Having multiple raft layers.
|
|
|
|
|
assert(m_num_interface_raft_layers > 0);
|
|
|
|
|
// Extend the raft base by the bases of the support columns, add the raft contacts.
|
|
|
|
|
raft_polygons = raft_interface_polygons;
|
|
|
|
|
//FIXME make the offset configurable.
|
|
|
|
|
polygons_append(raft_polygons, offset(columns_base.polygons, inflate_factor));
|
|
|
|
|
raft_polygons = union_(raft_polygons);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// No raft. The 1st intermediate layer contains the bases of the support columns.
|
|
|
|
|
// Expand the polygons, but trim with the object.
|
|
|
|
|
columns_base.polygons = diff(
|
|
|
|
|
offset(columns_base.polygons, inflate_factor),
|
|
|
|
|
offset(m_object->get_layer(0), safety_factor);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
return raft_polygons;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// Convert some of the intermediate layers into top/bottom interface layers.
|
2016-10-20 11:04:23 +00:00
|
|
|
|
PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_interface_layers(
|
2016-10-13 14:00:22 +00:00
|
|
|
|
const PrintObject &object,
|
2016-10-16 14:30:56 +00:00
|
|
|
|
const MyLayersPtr &bottom_contacts,
|
|
|
|
|
const MyLayersPtr &top_contacts,
|
|
|
|
|
MyLayersPtr &intermediate_layers,
|
|
|
|
|
MyLayerStorage &layer_storage) const
|
2016-10-13 14:00:22 +00:00
|
|
|
|
{
|
|
|
|
|
// Old comment:
|
|
|
|
|
// Compute interface area on this layer as diff of upper contact area
|
|
|
|
|
// (or upper interface area) and layer slices.
|
|
|
|
|
// This diff is responsible of the contact between support material and
|
|
|
|
|
// the top surfaces of the object. We should probably offset the top
|
|
|
|
|
// surfaces vertically before performing the diff, but this needs
|
|
|
|
|
// investigation.
|
|
|
|
|
|
|
|
|
|
// my $area_threshold = $self->interface_flow->scaled_spacing ** 2;
|
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
MyLayersPtr interface_layers;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// Contact layer is considered an interface layer, therefore run the following block only if support_material_interface_layers > 1.
|
|
|
|
|
if (! intermediate_layers.empty() && m_object_config->support_material_interface_layers > 1) {
|
|
|
|
|
// Index of the first top contact layer intersecting the current intermediate layer.
|
|
|
|
|
size_t idx_top_contact_first = 0;
|
|
|
|
|
// Index of the first bottom contact layer intersecting the current intermediate layer.
|
|
|
|
|
size_t idx_bottom_contact_first = 0;
|
|
|
|
|
// For all intermediate layers, collect top contact surfaces, which are not further than support_material_interface_layers.
|
|
|
|
|
//FIXME this could be parallelized.
|
|
|
|
|
for (size_t idx_intermediate_layer = 0; idx_intermediate_layer < intermediate_layers.size(); ++ idx_intermediate_layer) {
|
2016-10-16 14:30:56 +00:00
|
|
|
|
MyLayer &intermediate_layer = *intermediate_layers[idx_intermediate_layer];
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// Top / bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces.
|
|
|
|
|
coordf_t top_z = intermediate_layers[std::min<int>(intermediate_layers.size()-1, idx_intermediate_layer + m_object_config->support_material_interface_layers - 1)]->print_z;
|
|
|
|
|
coordf_t bottom_z = intermediate_layers[std::max<int>(0, int(idx_intermediate_layer) - int(m_object_config->support_material_interface_layers) + 1)]->bottom_z;
|
|
|
|
|
// Move idx_top_contact_first up until above the current print_z.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
while (idx_top_contact_first < top_contacts.size() && top_contacts[idx_top_contact_first]->print_z < intermediate_layer.print_z)
|
2016-10-13 14:00:22 +00:00
|
|
|
|
++ idx_top_contact_first;
|
|
|
|
|
// Collect the top contact areas above this intermediate layer, below top_z.
|
|
|
|
|
Polygons polygons_top_contact_projected;
|
|
|
|
|
for (size_t idx_top_contact = idx_top_contact_first; idx_top_contact < top_contacts.size(); ++ idx_top_contact) {
|
2016-10-16 14:30:56 +00:00
|
|
|
|
const MyLayer &top_contact_layer = *top_contacts[idx_top_contact];
|
2016-10-13 14:00:22 +00:00
|
|
|
|
if (top_contact_layer.bottom_z - EPSILON > top_z)
|
|
|
|
|
break;
|
|
|
|
|
polygons_append(polygons_top_contact_projected, top_contact_layer.polygons);
|
|
|
|
|
}
|
|
|
|
|
// Move idx_bottom_contact_first up until touching bottom_z.
|
|
|
|
|
while (idx_bottom_contact_first < bottom_contacts.size() && bottom_contacts[idx_bottom_contact_first]->print_z + EPSILON < bottom_z)
|
|
|
|
|
++ idx_bottom_contact_first;
|
|
|
|
|
// Collect the top contact areas above this intermediate layer, below top_z.
|
|
|
|
|
Polygons polygons_bottom_contact_projected;
|
|
|
|
|
for (size_t idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < bottom_contacts.size(); ++ idx_bottom_contact) {
|
2016-10-16 14:30:56 +00:00
|
|
|
|
const MyLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact];
|
|
|
|
|
if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z)
|
2016-10-13 14:00:22 +00:00
|
|
|
|
break;
|
|
|
|
|
polygons_append(polygons_bottom_contact_projected, bottom_contact_layer.polygons);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (polygons_top_contact_projected.empty() && polygons_bottom_contact_projected.empty())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Insert a new layer into top_interface_layers.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
MyLayer &layer_new = layer_allocate(layer_storage,
|
|
|
|
|
polygons_top_contact_projected.empty() ? sltBottomInterface : sltTopInterface);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
layer_new.print_z = intermediate_layer.print_z;
|
|
|
|
|
layer_new.bottom_z = intermediate_layer.bottom_z;
|
|
|
|
|
layer_new.height = intermediate_layer.height;
|
|
|
|
|
layer_new.bridging = intermediate_layer.bridging;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
interface_layers.push_back(&layer_new);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
polygons_append(polygons_top_contact_projected, polygons_bottom_contact_projected);
|
|
|
|
|
polygons_top_contact_projected = union_(polygons_top_contact_projected, true);
|
2016-10-16 14:30:56 +00:00
|
|
|
|
layer_new.polygons = intersection(intermediate_layer.polygons, polygons_top_contact_projected);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
//FIXME filter layer_new.polygons islands by a minimum area?
|
|
|
|
|
// $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ];
|
2016-10-16 14:30:56 +00:00
|
|
|
|
intermediate_layer.polygons = diff(intermediate_layer.polygons, polygons_top_contact_projected, false);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return interface_layers;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-20 11:04:23 +00:00
|
|
|
|
void PrintObjectSupportMaterial::generate_toolpaths(
|
2016-10-13 14:00:22 +00:00
|
|
|
|
const PrintObject &object,
|
2016-10-20 11:04:23 +00:00
|
|
|
|
const Polygons &raft,
|
2016-10-13 14:00:22 +00:00
|
|
|
|
const MyLayersPtr &bottom_contacts,
|
|
|
|
|
const MyLayersPtr &top_contacts,
|
|
|
|
|
const MyLayersPtr &intermediate_layers,
|
2016-10-16 14:30:56 +00:00
|
|
|
|
const MyLayersPtr &interface_layers) const
|
2016-10-13 14:00:22 +00:00
|
|
|
|
{
|
|
|
|
|
// Shape of the top contact area.
|
|
|
|
|
int n_contact_loops = 1;
|
2016-10-20 11:04:23 +00:00
|
|
|
|
coordf_t circle_radius = 1.5 * m_support_material_interface_flow.scaled_width();
|
2016-10-13 14:00:22 +00:00
|
|
|
|
coordf_t circle_distance = 3. * circle_radius;
|
|
|
|
|
Polygon circle;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
circle.points.reserve(6);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
for (size_t i = 0; i < 6; ++ i) {
|
2016-10-16 14:30:56 +00:00
|
|
|
|
double angle = double(i) * M_PI / 3.;
|
|
|
|
|
circle.points.push_back(Point(circle_radius * cos(angle), circle_radius * sin(angle)));
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Slic3r::debugf "Generating patterns\n";
|
|
|
|
|
|
|
|
|
|
// Prepare fillers.
|
|
|
|
|
SupportMaterialPattern support_pattern = m_object_config->support_material_pattern;
|
|
|
|
|
bool with_sheath = m_object_config->support_material_with_sheath;
|
|
|
|
|
InfillPattern infill_pattern;
|
|
|
|
|
std::vector<double> angles;
|
|
|
|
|
angles.push_back(m_object_config->support_material_angle);
|
|
|
|
|
switch (support_pattern) {
|
|
|
|
|
case smpRectilinearGrid:
|
|
|
|
|
angles.push_back(angles[0] + 90.);
|
|
|
|
|
// fall through
|
|
|
|
|
case smpRectilinear:
|
|
|
|
|
infill_pattern = ipRectilinear;
|
|
|
|
|
break;
|
|
|
|
|
case smpHoneycomb:
|
|
|
|
|
case smpPillars:
|
|
|
|
|
infill_pattern = ipHoneycomb;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
std::auto_ptr<Fill> filler_interface = std::auto_ptr<Fill>(Fill::new_from_type(ipRectilinear));
|
|
|
|
|
std::auto_ptr<Fill> filler_support = std::auto_ptr<Fill>(Fill::new_from_type(infill_pattern));
|
|
|
|
|
{
|
|
|
|
|
BoundingBox bbox_object = object.bounding_box();
|
2016-10-16 14:30:56 +00:00
|
|
|
|
filler_interface->set_bounding_box(bbox_object);
|
|
|
|
|
filler_support->set_bounding_box(bbox_object);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
coordf_t interface_angle = m_object_config->support_material_angle + 90.;
|
2016-10-20 11:04:23 +00:00
|
|
|
|
coordf_t interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_material_interface_flow.spacing();
|
|
|
|
|
coordf_t interface_density = (interface_spacing == 0.) ? 1. : (m_support_material_interface_flow.spacing() / interface_spacing);
|
|
|
|
|
coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_material_flow.spacing();
|
|
|
|
|
coordf_t support_density = (support_spacing == 0.) ? 1. : (m_support_material_flow.spacing() / support_spacing);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
//FIXME Parallelize the support generator:
|
|
|
|
|
/*
|
|
|
|
|
Slic3r::parallelize(
|
|
|
|
|
threads => $self->print_config->threads,
|
2016-10-16 14:30:56 +00:00
|
|
|
|
items => [ 0 .. n_$object.support_layers} ],
|
2016-10-13 14:00:22 +00:00
|
|
|
|
thread_cb => sub {
|
|
|
|
|
my $q = shift;
|
|
|
|
|
while (defined (my $layer_id = $q->dequeue)) {
|
|
|
|
|
$process_layer->($layer_id);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
no_threads_cb => sub {
|
2016-10-16 14:30:56 +00:00
|
|
|
|
$process_layer->($_) for 0 .. n_{$object.support_layers};
|
2016-10-13 14:00:22 +00:00
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
*/
|
|
|
|
|
// Indices of the 1st layer in their respective container at the support layer height.
|
|
|
|
|
size_t idx_layer_bottom_contact = 0;
|
|
|
|
|
size_t idx_layer_top_contact = 0;
|
|
|
|
|
size_t idx_layer_intermediate = 0;
|
|
|
|
|
size_t idx_layer_inteface = 0;
|
|
|
|
|
for (size_t support_layer_id = 0; support_layer_id < object.support_layers.size(); ++ support_layer_id)
|
|
|
|
|
{
|
|
|
|
|
SupportLayer &support_layer = *object.support_layers[support_layer_id];
|
|
|
|
|
|
|
|
|
|
// Find polygons with the same print_z.
|
|
|
|
|
Polygons bottom_contact_polygons;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
Polygons top_contact_polygons;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
Polygons base_polygons;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
Polygons interface_polygons;
|
|
|
|
|
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// Increment the layer indices to find a layer at support_layer.print_z.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
for (; idx_layer_bottom_contact < bottom_contacts .size() && bottom_contacts [idx_layer_bottom_contact]->print_z < support_layer.print_z - EPSILON; ++ idx_layer_bottom_contact) ;
|
|
|
|
|
for (; idx_layer_top_contact < top_contacts .size() && top_contacts [idx_layer_top_contact ]->print_z < support_layer.print_z - EPSILON; ++ idx_layer_top_contact ) ;
|
|
|
|
|
for (; idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate ]->print_z < support_layer.print_z - EPSILON; ++ idx_layer_intermediate ) ;
|
|
|
|
|
for (; idx_layer_inteface < interface_layers .size() && interface_layers [idx_layer_inteface ]->print_z < support_layer.print_z - EPSILON; ++ idx_layer_inteface ) ;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// Copy polygons from the layers.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
if (idx_layer_bottom_contact < bottom_contacts.size() && bottom_contacts[idx_layer_bottom_contact]->print_z < support_layer.print_z + EPSILON)
|
|
|
|
|
bottom_contact_polygons = bottom_contacts[idx_layer_bottom_contact]->polygons;
|
|
|
|
|
if (idx_layer_top_contact < top_contacts.size() && top_contacts[idx_layer_top_contact]->print_z < support_layer.print_z + EPSILON)
|
|
|
|
|
top_contact_polygons = top_contacts[idx_layer_top_contact]->polygons;
|
|
|
|
|
if (idx_layer_inteface < interface_layers.size() && interface_layers[idx_layer_inteface]->print_z < support_layer.print_z + EPSILON)
|
|
|
|
|
interface_polygons = interface_layers[idx_layer_inteface]->polygons;
|
|
|
|
|
if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON)
|
|
|
|
|
base_polygons = intermediate_layers[idx_layer_intermediate]->polygons;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
// We redefine flows locally by applying this layer's height.
|
2016-10-20 11:04:23 +00:00
|
|
|
|
Flow flow = m_support_material_flow;
|
|
|
|
|
Flow interface_flow = m_support_material_interface_flow;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
flow.height = support_layer.height;
|
|
|
|
|
interface_flow.height = support_layer.height;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
if (1) {
|
|
|
|
|
require "Slic3r/SVG.pm";
|
|
|
|
|
Slic3r::SVG::output("out\\layer_" . $z . ".svg",
|
|
|
|
|
blue_expolygons => union_ex($base),
|
|
|
|
|
red_expolygons => union_ex($contact),
|
|
|
|
|
green_expolygons => union_ex($interface),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Store inslands, over which the retract will be disabled.
|
|
|
|
|
{
|
|
|
|
|
Polygons polys(bottom_contact_polygons);
|
|
|
|
|
polygons_append(polys, interface_polygons);
|
|
|
|
|
polygons_append(polys, base_polygons);
|
|
|
|
|
polygons_append(polys, top_contact_polygons);
|
|
|
|
|
ExPolygons islands = union_ex(polys);
|
|
|
|
|
support_layer.support_islands.expolygons.insert(support_layer.support_islands.expolygons.end(), islands.begin(), islands.end());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Polygons contact_infill_polygons;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
if (! top_contact_polygons.empty())
|
2016-10-13 14:00:22 +00:00
|
|
|
|
{
|
|
|
|
|
// Having a top interface layer.
|
|
|
|
|
if (m_object_config->support_material_interface_layers == 0)
|
|
|
|
|
// If no interface layers were requested, we treat the contact layer exactly as a generic base layer.
|
|
|
|
|
polygons_append(base_polygons, top_contact_polygons);
|
|
|
|
|
else if (n_contact_loops == 0)
|
|
|
|
|
// If no loops are allowed, we treat the contact layer exactly as a generic interface layer.
|
|
|
|
|
polygons_append(interface_polygons, top_contact_polygons);
|
|
|
|
|
else if (! top_contact_polygons.empty())
|
|
|
|
|
{
|
|
|
|
|
// Create loop paths and
|
|
|
|
|
Polygons overhang_polygons = (top_contacts[idx_layer_top_contact]->aux_polygons == NULL) ?
|
|
|
|
|
Polygons() :
|
|
|
|
|
*top_contacts[idx_layer_top_contact]->aux_polygons;
|
|
|
|
|
|
|
|
|
|
// Generate the outermost loop.
|
|
|
|
|
// Find centerline of the external loop (or any other kind of extrusions should the loop be skipped)
|
2016-10-16 14:30:56 +00:00
|
|
|
|
top_contact_polygons = offset(top_contact_polygons, - 0.5 * interface_flow.scaled_width());
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
Polygons loops0;
|
|
|
|
|
{
|
|
|
|
|
// find centerline of the external loop of the contours
|
|
|
|
|
// only consider the loops facing the overhang
|
|
|
|
|
Polygons external_loops;
|
|
|
|
|
// Positions of the loop centers.
|
|
|
|
|
Polygons circles;
|
|
|
|
|
{
|
2016-10-16 14:30:56 +00:00
|
|
|
|
Polygons overhang_with_margin = offset(overhang_polygons, 0.5 * interface_flow.scaled_width());
|
|
|
|
|
for (Polygons::const_iterator it_contact = top_contact_polygons.begin(); it_contact != top_contact_polygons.end(); ++ it_contact) {
|
|
|
|
|
Polylines tmp;
|
|
|
|
|
tmp.push_back(it_contact->split_at_first_point());
|
|
|
|
|
if (! intersection(tmp, overhang_with_margin).empty()) {
|
2016-10-13 14:00:22 +00:00
|
|
|
|
external_loops.push_back(*it_contact);
|
|
|
|
|
Points positions_new = it_contact->equally_spaced_points(circle_distance);
|
|
|
|
|
for (Points::const_iterator it_center = positions_new.begin(); it_center != positions_new.end(); ++ it_center) {
|
|
|
|
|
circles.push_back(circle);
|
|
|
|
|
Polygon &circle_new = circles.back();
|
2016-10-16 14:30:56 +00:00
|
|
|
|
for (size_t i = 0; i < circle_new.points.size(); ++ i)
|
|
|
|
|
circle_new.points[i].translate(*it_center);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-10-16 14:30:56 +00:00
|
|
|
|
}
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
// Apply a pattern to the loop.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
loops0 = diff(external_loops, circles);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-16 14:30:56 +00:00
|
|
|
|
Polylines loop_lines;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
{
|
2016-10-16 14:30:56 +00:00
|
|
|
|
// make more loops
|
|
|
|
|
Polygons loop_polygons = loops0;
|
|
|
|
|
for (size_t i = 1; i < n_contact_loops; ++ i)
|
|
|
|
|
polygons_append(loop_polygons,
|
|
|
|
|
offset2(
|
|
|
|
|
loops0,
|
|
|
|
|
- int(i) * interface_flow.scaled_spacing() - 0.5 * interface_flow.scaled_spacing(),
|
|
|
|
|
0.5 * interface_flow.scaled_spacing()));
|
|
|
|
|
// clip such loops to the side oriented towards the object
|
|
|
|
|
loop_lines.reserve(loop_polygons.size());
|
|
|
|
|
for (Polygons::const_iterator it = loop_polygons.begin(); it != loop_polygons.end(); ++ it)
|
|
|
|
|
loop_lines.push_back(it->split_at_first_point());
|
|
|
|
|
loop_lines = intersection(loop_lines, offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN)));
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// add the contact infill area to the interface area
|
|
|
|
|
// note that growing loops by $circle_radius ensures no tiny
|
|
|
|
|
// extrusions are left inside the circles; however it creates
|
|
|
|
|
// a very large gap between loops and contact_infill_polygons, so maybe another
|
|
|
|
|
// solution should be found to achieve both goals
|
2016-10-16 14:30:56 +00:00
|
|
|
|
{
|
|
|
|
|
Polygons loop_polygons;
|
|
|
|
|
offset(loop_lines, &loop_polygons, circle_radius * 1.1);
|
|
|
|
|
contact_infill_polygons = diff(top_contact_polygons, loop_polygons);
|
|
|
|
|
}
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
// Transform loops into ExtrusionPath objects.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
for (Polylines::const_iterator it_polyline = loop_lines.begin(); it_polyline != loop_lines.end(); ++ it_polyline) {
|
2016-10-13 14:00:22 +00:00
|
|
|
|
ExtrusionPath *extrusion_path = new ExtrusionPath(erSupportMaterialInterface);
|
|
|
|
|
support_layer.support_interface_fills.entities.push_back(extrusion_path);
|
2016-10-16 14:30:56 +00:00
|
|
|
|
extrusion_path->polyline = *it_polyline;
|
|
|
|
|
extrusion_path->mm3_per_mm = interface_flow.mm3_per_mm();
|
2016-10-13 14:00:22 +00:00
|
|
|
|
extrusion_path->width = interface_flow.width;
|
|
|
|
|
extrusion_path->height = support_layer.height;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// interface and contact infill
|
|
|
|
|
if (! interface_polygons.empty() || ! contact_infill_polygons.empty()) {
|
|
|
|
|
//FIXME When paralellizing, each thread shall have its own copy of the fillers.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
filler_interface->angle = interface_angle;
|
|
|
|
|
filler_interface->spacing = interface_flow.spacing();
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
// find centerline of the external loop
|
2016-10-16 14:30:56 +00:00
|
|
|
|
interface_polygons = offset2(interface_polygons, SCALED_EPSILON, - SCALED_EPSILON - 0.5 * interface_flow.scaled_width());
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// join regions by offsetting them to ensure they're merged
|
|
|
|
|
polygons_append(interface_polygons, contact_infill_polygons);
|
|
|
|
|
interface_polygons = offset(interface_polygons, SCALED_EPSILON);
|
|
|
|
|
|
|
|
|
|
// turn base support into interface when it's contained in our holes
|
|
|
|
|
// (this way we get wider interface anchoring)
|
|
|
|
|
{
|
|
|
|
|
Polygons interface_polygons_new;
|
|
|
|
|
interface_polygons_new.reserve(interface_polygons.size());
|
|
|
|
|
for (Polygons::iterator it_polygon = interface_polygons.begin(); it_polygon != interface_polygons.end(); ++ it_polygon) {
|
2016-10-16 14:30:56 +00:00
|
|
|
|
if (it_polygon->is_clockwise()) {
|
2016-10-13 14:00:22 +00:00
|
|
|
|
Polygons hole;
|
|
|
|
|
hole.push_back(*it_polygon);
|
|
|
|
|
hole.back().make_counter_clockwise();
|
|
|
|
|
if (diff(hole, base_polygons, true).empty())
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
interface_polygons_new.push_back(Polygon());
|
2016-10-16 14:30:56 +00:00
|
|
|
|
interface_polygons_new.back().points.swap(it_polygon->points);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
interface_polygons.swap(interface_polygons_new);
|
|
|
|
|
}
|
|
|
|
|
base_polygons = diff(base_polygons, interface_polygons);
|
|
|
|
|
|
|
|
|
|
ExPolygons to_fill = union_ex(interface_polygons);
|
|
|
|
|
for (ExPolygons::const_iterator it_expolygon = to_fill.begin(); it_expolygon != to_fill.end(); ++ it_expolygon) {
|
2016-10-17 07:34:48 +00:00
|
|
|
|
Surface surface(stInternal, *it_expolygon);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
FillParams fill_params;
|
|
|
|
|
fill_params.density = interface_density;
|
|
|
|
|
fill_params.complete = true;
|
2016-10-17 07:34:48 +00:00
|
|
|
|
Polylines polylines = filler_interface->fill_surface(&surface, fill_params);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
for (Polylines::const_iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) {
|
|
|
|
|
ExtrusionPath *extrusion_path = new ExtrusionPath(erSupportMaterialInterface);
|
|
|
|
|
support_layer.support_interface_fills.entities.push_back(extrusion_path);
|
2016-10-16 14:30:56 +00:00
|
|
|
|
extrusion_path->polyline = *it_polyline;
|
|
|
|
|
extrusion_path->mm3_per_mm = interface_flow.mm3_per_mm();
|
2016-10-13 14:00:22 +00:00
|
|
|
|
extrusion_path->width = interface_flow.width;
|
|
|
|
|
extrusion_path->height = support_layer.height;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// support or flange
|
|
|
|
|
if (! base_polygons.empty()) {
|
|
|
|
|
//FIXME When paralellizing, each thread shall have its own copy of the fillers.
|
|
|
|
|
Fill *filler = filler_support.get();
|
2016-10-16 14:30:56 +00:00
|
|
|
|
filler->angle = angles[support_layer_id % angles.size()];
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// We don't use $base_flow->spacing because we need a constant spacing
|
|
|
|
|
// value that guarantees that all layers are correctly aligned.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
filler->spacing = flow.spacing();
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
coordf_t density = support_density;
|
|
|
|
|
Flow base_flow = flow;
|
|
|
|
|
|
|
|
|
|
// find centerline of the external loop/extrusions
|
2016-10-16 14:30:56 +00:00
|
|
|
|
ExPolygons to_infill = offset2_ex(base_polygons, SCALED_EPSILON, - SCALED_EPSILON - 0.5*flow.scaled_width());
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
if (1) {
|
|
|
|
|
require "Slic3r/SVG.pm";
|
|
|
|
|
Slic3r::SVG::output("out\\to_infill_base" . $z . ".svg",
|
|
|
|
|
red_expolygons => union_ex($contact),
|
|
|
|
|
green_expolygons => union_ex($interface),
|
|
|
|
|
blue_expolygons => $to_infill,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (support_layer_id == 0) {
|
|
|
|
|
// Base flange.
|
|
|
|
|
filler = filler_interface.get();
|
2016-10-16 14:30:56 +00:00
|
|
|
|
filler->angle = m_object_config->support_material_angle + 90.;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
density = 0.5;
|
|
|
|
|
base_flow = m_first_layer_flow;
|
|
|
|
|
// use the proper spacing for first layer as we don't need to align
|
|
|
|
|
// its pattern to the other layers
|
|
|
|
|
//FIXME When paralellizing, each thread shall have its own copy of the fillers.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
filler->spacing = base_flow.spacing();
|
2016-10-13 14:00:22 +00:00
|
|
|
|
} else if (with_sheath) {
|
|
|
|
|
// Draw a perimeter all around the support infill. This makes the support stable, but difficult to remove.
|
|
|
|
|
// TODO: use brim ordering algorithm
|
2016-10-16 14:30:56 +00:00
|
|
|
|
Polygons to_infill_polygons = to_polygons(to_infill);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
for (Polygons::const_iterator it_polyline = to_infill_polygons.begin(); it_polyline != to_infill_polygons.end(); ++ it_polyline) {
|
|
|
|
|
ExtrusionPath *extrusion_path = new ExtrusionPath(erSupportMaterial);
|
|
|
|
|
support_layer.support_fills.entities.push_back(extrusion_path);
|
2016-10-16 14:30:56 +00:00
|
|
|
|
extrusion_path->polyline = *it_polyline;
|
|
|
|
|
extrusion_path->mm3_per_mm = flow.mm3_per_mm();
|
2016-10-13 14:00:22 +00:00
|
|
|
|
extrusion_path->width = flow.width;
|
|
|
|
|
extrusion_path->height = support_layer.height;
|
|
|
|
|
}
|
|
|
|
|
// TODO: use offset2_ex()
|
2016-10-16 14:30:56 +00:00
|
|
|
|
to_infill = offset_ex(to_infill_polygons, - flow.scaled_spacing());
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (ExPolygons::const_iterator it_expolygon = to_infill.begin(); it_expolygon != to_infill.end(); ++ it_expolygon) {
|
2016-10-17 07:34:48 +00:00
|
|
|
|
Surface surface(stInternal, *it_expolygon);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
FillParams fill_params;
|
|
|
|
|
fill_params.density = density;
|
|
|
|
|
fill_params.complete = true;
|
2016-10-17 07:34:48 +00:00
|
|
|
|
Polylines polylines = filler->fill_surface(&surface, fill_params);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
for (Polylines::const_iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) {
|
|
|
|
|
ExtrusionPath *extrusion_path = new ExtrusionPath(erSupportMaterial);
|
|
|
|
|
support_layer.support_fills.entities.push_back(extrusion_path);
|
2016-10-16 14:30:56 +00:00
|
|
|
|
extrusion_path->polyline = *it_polyline;
|
|
|
|
|
extrusion_path->mm3_per_mm = base_flow.mm3_per_mm();
|
2016-10-13 14:00:22 +00:00
|
|
|
|
extrusion_path->width = base_flow.width;
|
|
|
|
|
extrusion_path->height = support_layer.height;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
}
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// support or flange
|
|
|
|
|
if (! bottom_contact_polygons.empty()) {
|
|
|
|
|
//FIXME When paralellizing, each thread shall have its own copy of the fillers.
|
|
|
|
|
Fill *filler = filler_support.get();
|
2016-10-16 14:30:56 +00:00
|
|
|
|
filler->angle = angles[support_layer_id % angles.size()];
|
2016-10-13 14:00:22 +00:00
|
|
|
|
// We don't use $base_flow->spacing because we need a constant spacing
|
|
|
|
|
// value that guarantees that all layers are correctly aligned.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
filler->spacing = flow.spacing();
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
coordf_t density = support_density;
|
|
|
|
|
Flow base_flow = flow;
|
|
|
|
|
|
|
|
|
|
// find centerline of the external loop/extrusions
|
2016-10-16 14:30:56 +00:00
|
|
|
|
ExPolygons to_infill = offset2_ex(base_polygons, SCALED_EPSILON, - SCALED_EPSILON - 0.5*flow.scaled_width());
|
2016-10-13 14:00:22 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
if (1) {
|
|
|
|
|
require "Slic3r/SVG.pm";
|
|
|
|
|
Slic3r::SVG::output("out\\to_infill_base" . $z . ".svg",
|
|
|
|
|
red_expolygons => union_ex($contact),
|
|
|
|
|
green_expolygons => union_ex($interface),
|
|
|
|
|
blue_expolygons => $to_infill,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (support_layer_id == 0) {
|
|
|
|
|
// Base flange.
|
|
|
|
|
filler = filler_interface.get();
|
2016-10-16 14:30:56 +00:00
|
|
|
|
filler->angle = m_object_config->support_material_angle + 90.;
|
2016-10-13 14:00:22 +00:00
|
|
|
|
density = 0.5;
|
|
|
|
|
base_flow = m_first_layer_flow;
|
|
|
|
|
// use the proper spacing for first layer as we don't need to align
|
|
|
|
|
// its pattern to the other layers
|
|
|
|
|
//FIXME When paralellizing, each thread shall have its own copy of the fillers.
|
2016-10-16 14:30:56 +00:00
|
|
|
|
filler->spacing = base_flow.spacing();
|
2016-10-13 14:00:22 +00:00
|
|
|
|
} else if (with_sheath) {
|
|
|
|
|
// Draw a perimeter all around the support infill. This makes the support stable, but difficult to remove.
|
|
|
|
|
// TODO: use brim ordering algorithm
|
2016-10-16 14:30:56 +00:00
|
|
|
|
Polygons to_infill_polygons = to_polygons(to_infill);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
for (Polygons::const_iterator it_polyline = to_infill_polygons.begin(); it_polyline != to_infill_polygons.end(); ++ it_polyline) {
|
|
|
|
|
ExtrusionPath *extrusion_path = new ExtrusionPath(erSupportMaterial);
|
|
|
|
|
support_layer.support_fills.entities.push_back(extrusion_path);
|
2016-10-16 14:30:56 +00:00
|
|
|
|
extrusion_path->polyline = *it_polyline;
|
|
|
|
|
extrusion_path->mm3_per_mm = flow.mm3_per_mm();
|
2016-10-13 14:00:22 +00:00
|
|
|
|
extrusion_path->width = flow.width;
|
|
|
|
|
extrusion_path->height = support_layer.height;
|
|
|
|
|
}
|
|
|
|
|
// TODO: use offset2_ex()
|
2016-10-16 14:30:56 +00:00
|
|
|
|
to_infill = offset_ex(to_infill_polygons, - flow.scaled_spacing());
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (ExPolygons::const_iterator it_expolygon = to_infill.begin(); it_expolygon != to_infill.end(); ++ it_expolygon) {
|
2016-10-17 07:34:48 +00:00
|
|
|
|
Surface surface(stInternal, *it_expolygon);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
FillParams fill_params;
|
|
|
|
|
fill_params.density = density;
|
|
|
|
|
fill_params.complete = true;
|
2016-10-17 07:34:48 +00:00
|
|
|
|
Polylines polylines = filler->fill_surface(&surface, fill_params);
|
2016-10-13 14:00:22 +00:00
|
|
|
|
for (Polylines::const_iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) {
|
|
|
|
|
ExtrusionPath *extrusion_path = new ExtrusionPath(erSupportMaterial);
|
|
|
|
|
support_layer.support_fills.entities.push_back(extrusion_path);
|
2016-10-16 14:30:56 +00:00
|
|
|
|
extrusion_path->polyline = *it_polyline;
|
|
|
|
|
extrusion_path->mm3_per_mm = base_flow.mm3_per_mm();
|
2016-10-13 14:00:22 +00:00
|
|
|
|
extrusion_path->width = base_flow.width;
|
|
|
|
|
extrusion_path->height = support_layer.height;
|
2016-10-16 14:30:56 +00:00
|
|
|
|
}
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
if (0) {
|
|
|
|
|
require "Slic3r/SVG.pm";
|
|
|
|
|
Slic3r::SVG::output("islands_" . $z . ".svg",
|
|
|
|
|
red_expolygons => union_ex($contact),
|
|
|
|
|
green_expolygons => union_ex($interface),
|
|
|
|
|
green_polylines => [ map $_->unpack->polyline, @{$layer->support_contact_fills} ],
|
|
|
|
|
polylines => [ map $_->unpack->polyline, @{$layer->support_fills} ],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
*/
|
2016-10-16 14:30:56 +00:00
|
|
|
|
} // for each support_layer_id
|
2016-10-13 14:00:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
2016-10-20 11:04:23 +00:00
|
|
|
|
void PrintObjectSupportMaterial::clip_by_pillars(
|
2016-10-13 14:00:22 +00:00
|
|
|
|
const PrintObject &object,
|
|
|
|
|
LayersPtr &bottom_contacts,
|
|
|
|
|
LayersPtr &top_contacts,
|
|
|
|
|
LayersPtr &intermediate_contacts);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
// this prevents supplying an empty point set to BoundingBox constructor
|
|
|
|
|
if (top_contacts.empty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
coord_t pillar_size = scale_(PILLAR_SIZE);
|
|
|
|
|
coord_t pillar_spacing = scale_(PILLAR_SPACING);
|
|
|
|
|
|
|
|
|
|
// A regular grid of pillars, filling the 2D bounding box.
|
|
|
|
|
Polygons grid;
|
|
|
|
|
{
|
|
|
|
|
// Rectangle with a side of 2.5x2.5mm.
|
|
|
|
|
Polygon pillar;
|
|
|
|
|
pillar.points.push_back(Point(0, 0));
|
|
|
|
|
pillar.points.push_back(Point(pillar_size, 0));
|
|
|
|
|
pillar.points.push_back(Point(pillar_size, pillar_size));
|
|
|
|
|
pillar.points.push_back(Point(0, pillar_size));
|
|
|
|
|
|
|
|
|
|
// 2D bounding box of the projection of all contact polygons.
|
|
|
|
|
BoundingBox bbox;
|
|
|
|
|
for (LayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it)
|
|
|
|
|
bbox.merge(get_extents((*it)->polygons));
|
|
|
|
|
grid.reserve(size_t(ceil(bb.size().x / pillar_spacing)) * size_t(ceil(bb.size().y / pillar_spacing)));
|
|
|
|
|
for (coord_t x = bb.min.x; x <= bb.max.x - pillar_size; x += pillar_spacing) {
|
|
|
|
|
for (coord_t y = bb.min.y; y <= bb.max.y - pillar_size; y += pillar_spacing) {
|
|
|
|
|
grid.push_back(pillar);
|
|
|
|
|
for (size_t i = 0; i < pillar.points.size(); ++ i)
|
|
|
|
|
grid.back().points[i].translate(Point(x, y));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// add pillars to every layer
|
|
|
|
|
for my $i (0..n_support_z) {
|
|
|
|
|
$shape->[$i] = [ @$grid ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// build capitals
|
|
|
|
|
for my $i (0..n_support_z) {
|
|
|
|
|
my $z = $support_z->[$i];
|
|
|
|
|
|
|
|
|
|
my $capitals = intersection(
|
|
|
|
|
$grid,
|
|
|
|
|
$contact->{$z} // [],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// work on one pillar at time (if any) to prevent the capitals from being merged
|
|
|
|
|
// but store the contact area supported by the capital because we need to make
|
|
|
|
|
// sure nothing is left
|
|
|
|
|
my $contact_supported_by_capitals = [];
|
|
|
|
|
foreach my $capital (@$capitals) {
|
|
|
|
|
// enlarge capital tops
|
|
|
|
|
$capital = offset([$capital], +($pillar_spacing - $pillar_size)/2);
|
|
|
|
|
push @$contact_supported_by_capitals, @$capital;
|
|
|
|
|
|
|
|
|
|
for (my $j = $i-1; $j >= 0; $j--) {
|
|
|
|
|
my $jz = $support_z->[$j];
|
|
|
|
|
$capital = offset($capital, -$self->interface_flow->scaled_width/2);
|
|
|
|
|
last if !@$capitals;
|
|
|
|
|
push @{ $shape->[$j] }, @$capital;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Capitals will not generally cover the whole contact area because there will be
|
|
|
|
|
// remainders. For now we handle this situation by projecting such unsupported
|
|
|
|
|
// areas to the ground, just like we would do with a normal support.
|
|
|
|
|
my $contact_not_supported_by_capitals = diff(
|
|
|
|
|
$contact->{$z} // [],
|
|
|
|
|
$contact_supported_by_capitals,
|
|
|
|
|
);
|
|
|
|
|
if (@$contact_not_supported_by_capitals) {
|
|
|
|
|
for (my $j = $i-1; $j >= 0; $j--) {
|
|
|
|
|
push @{ $shape->[$j] }, @$contact_not_supported_by_capitals;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sub clip_with_shape {
|
|
|
|
|
my ($self, $support, $shape) = @_;
|
|
|
|
|
|
|
|
|
|
foreach my $i (keys %$support) {
|
|
|
|
|
// don't clip bottom layer with shape so that we
|
|
|
|
|
// can generate a continuous base flange
|
|
|
|
|
// also don't clip raft layers
|
|
|
|
|
next if $i == 0;
|
|
|
|
|
next if $i < $self->object_config->raft_layers;
|
|
|
|
|
$support->{$i} = intersection(
|
|
|
|
|
$support->{$i},
|
|
|
|
|
$shape->[$i],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
} // namespace Slic3r
|