From 8f40d9b34ea75c5e772dfb4aab18554c49536f8c Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 13 Oct 2016 16:00:22 +0200 Subject: [PATCH] Initial implementation of C++ supports, some documentation of the existing code. --- xs/src/libslic3r/Fill/FillBase.cpp | 44 +- xs/src/libslic3r/Print.hpp | 4 +- xs/src/libslic3r/SupportMaterial.cpp | 1403 ++++++++++++++++++++++++++ xs/src/libslic3r/SupportMaterial.hpp | 187 +++- xs/src/libslic3r/Surface.hpp | 22 +- 5 files changed, 1632 insertions(+), 28 deletions(-) create mode 100644 xs/src/libslic3r/SupportMaterial.cpp diff --git a/xs/src/libslic3r/Fill/FillBase.cpp b/xs/src/libslic3r/Fill/FillBase.cpp index a0b351535..3c652fe85 100644 --- a/xs/src/libslic3r/Fill/FillBase.cpp +++ b/xs/src/libslic3r/Fill/FillBase.cpp @@ -2,6 +2,7 @@ #include "../ClipperUtils.hpp" #include "../Surface.hpp" +#include "../PrintConfig.hpp" #include "FillBase.hpp" #include "FillConcentric.hpp" @@ -15,28 +16,27 @@ namespace Slic3r { Fill* Fill::new_from_type(const std::string &type) { - if (type == "concentric") - return new FillConcentric(); - if (type == "honeycomb") - return new FillHoneycomb(); - if (type == "3dhoneycomb") - return new Fill3DHoneycomb(); - if (type == "rectilinear") -// return new FillRectilinear(); - return new FillRectilinear2(); - if (type == "line") - return new FillLine(); - if (type == "grid") -// return new FillGrid(); - return new FillGrid2(); - if (type == "archimedeanchords") - return new FillArchimedeanChords(); - if (type == "hilbertcurve") - return new FillHilbertCurve(); - if (type == "octagramspiral") - return new FillOctagramSpiral(); - CONFESS("unknown type"); - return NULL; + static t_config_enum_values enum_keys_map = ConfigOptionEnum::get_enum_values(); + t_config_enum_values::const_iterator it = enum_keys_map.find(type); + return (it == enum_keys_map.end()) ? NULL : new_from_type(InfillPattern(it->second)); +} + +Fill* Fill::new_from_type(const InfillPattern type) +{ + switch (type) { + case ipConcentric: return new FillConcentric(); + case ipHoneycomb: return new FillHoneycomb(); + case ip3DHoneycomb: return new Fill3DHoneycomb(); + case ipRectilinear: return new FillRectilinear2(); +// case ipRectilinear: return new FillRectilinear(); + case ipLine: return new FillLine(); + case ipGrid: return new FillGrid2(); +// case ipGrid: return new FillGrid(); + case ipArchimedeanChords: return new FillArchimedeanChords(); + case ipHilbertCurve: return new FillHilbertCurve(); + case ipOctagramSpiral: return new FillOctagramSpiral(); + default: CONFESS("unknown type"); return NULL; + } } Polylines Fill::fill_surface(const Surface *surface, const FillParams ¶ms) diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 311d0b8a5..755b39e9d 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -76,7 +76,7 @@ class PrintObject { friend class Print; - public: +public: // map of (vectors of volume ids), indexed by region_id /* (we use map instead of vector so that we don't have to worry about resizing it and the [] operator adds new items automagically) */ @@ -141,7 +141,7 @@ class PrintObject void discover_vertical_shells(); void bridge_over_infill(); - private: +private: Print* _print; ModelObject* _model_object; Points _copies; // Slic3r::Point objects in scaled G-code coordinates diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp new file mode 100644 index 000000000..39245605e --- /dev/null +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -0,0 +1,1403 @@ +#include "PerimeterGenerator.hpp" +#include "ClipperUtils.hpp" +#include "ExtrusionEntityCollection.hpp" +#include +#include + +namespace Slic3r { + +// increment used to reach MARGIN in steps to avoid trespassing thin objects +#define MARGIN_STEPS 3 +// generate a tree-like structure to save material +#define PILLAR_SIZE (2.5) +#define PILLAR_SPACING 10 + +inline Layer& layer_allocate(std::deque &layer_storage) +{ + m_layer_storage.push_back(Layer()); + return m_layer.back(); +} + +void PrintSupportMaterial::generate(PrintObject *object) +{ + coordf_t max_object_layer_height = 0.; + 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); + + if (m_support_layer_height_max == 0) + m_support_layer_height_max = std::max(max_object_layer_height, 0.75 * m_flow.nozzle_diameter); + if (m_support_interface_layer_height_max == 0) + m_support_interface_layer_height_max = std::max(max_object_layer_height, 0.75 * m_interface_flow.nozzle_diameter); + + // Layer instances will be allocated by std::deque and they will be kept until the end of this function call. + // The layers will be referenced by various LayersPtr (std::vector) + std::deque layer_storage; + + // Determine the top surfaces of the support, defined as: + // 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. + LayersSet top_contacts = this->top_contact_layers(object, layer_storage); + if (top_contacts.empty()) + return; + + // Determine the top surfaces of the object. We need these to determine + // the layer heights of support material and to clip support to the object + // silhouette. + LayersSet bottom_contacts = this->bottom_contact_layers(object, top_contacts, layer_storage); + + this->trim_top_contacts_by_bottom_contacts(object, bottom_contacts, top_contacts); + + // We now know the upper and lower boundaries for our support material object + // (@$contact_z and @$top_z), so we can generate intermediate layers. + LayersSet support_layers = this->support_layers(object, bottom_contacts, top_contacts, layer_storage, max_object_layer_height); + + // 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; + if (this->object_config->support_material_pattern.value == smpPillars) + shape = this->generate_pillars_shape(contact, support_z); + + // Propagate contact layers downwards to generate interface layers + LayersSet interface = this->generate_top_interface_layers(support_z, contact, top); + this->clip_with_object(interface, support_z, object); + if (! shape.empty()) + this->clip_with_shape(interface, shape); + + // Propagate contact layers and interface layers downwards to generate + // the main support layers. + LayersSet base = this->generate_base_layers(object, bottom_contacts, top_contacts, intermediate_layers, layer_storage); + if (! shape.empty()) + this->clip_with_shape(base, shape); + + // Install support layers into object. + for (size_t i = 0; i < support_z.size(); ++ i) { + object->add_support_layer( + i, // id + (i == 0) ? support_z[i] : (support_z[i] - support_z[i-1]), // height + support_z[i] // print_z + ); + if (i > 0) { + SupportLayer *sl1 = object->get_support_layer[object->support_layer_count()-2]; + SupportLayer *sl2 = object->get_support_layer[object->support_layer_count()-1]; + sl1->set_upper_layer(sl2); + sl2->set_lower_layer(sl1); + } + } + + // Generate the actual toolpaths and save them into each layer. + this->generate_toolpaths(object, overhang, contact, interface, base); +} + +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; + collect_regon_slices_by_type(layer, surface_type, out); + return out; +} + +void collect_region_slices_all(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); + const SurfaceCollection &slices = region.slices; + for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) + n_polygons_new += it->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; + out.push_back(surface.expolygon.contour); + out.insert(out.end(), surface.expolygon.holes.begin(), surface.expolygon.holes.end()); + } + } +} + +Polygons collect_region_slices_all(const Layer &layer) +{ + Polygons out; + collect_region_slices_all(layer, out); + 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; +} + +void collect_layer_slices_all(const Layer &layer, Polygons &out) +{ + // 1) Count the new polygons first. + size_t n_polygons_new = 0; + const SurfaceCollection &slices = region.slices; + for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) + n_polygons_new += it->expolygon.holes.size() + 1; + + // 2) Collect the new polygons. + for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) { + const Surface &surface = *it; + out.push_back(surface.expolygon.contour); + out.insert(out.end(), surface.expolygon.holes.begin(), surface.expolygon.holes.end()); + } +} + +Polygons collect_layer_slices_all(const Layer &layer) +{ + Polygons out; + collect_layer_slices_all(layer, out); + return out; +} + +PrintSupportMaterial::LayersPtr PrintSupportMaterial::top_contact_layers(const PrintObject &object, std::deque &layer_storage) const +{ + // Output layers, sorte by top Z. + LayersPtr contact_out; + + // if user specified a custom angle threshold, convert it to radians + double threshold_rad = 0.; + if (this->object_config->support_material_threshold > 0) { + threshold_rad = M_PI * double(this->object_config->support_material_threshold + 1) / 180.; // +1 makes the threshold inclusive + // 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. + bool buildplate_only = this->object_config->support_material && this->object_config->support_material_buildplate_only; + Polygons buildplate_only_top_surfaces; + +/* + Layer *first_object_layer = object.get_layer(this->object_config->raft_layers); + if (first_object_layer == NULL) + // Nothing to print, nothing to support. + return contact_out; + + // If printing over raft, make sure the bottom of the contact surfaces are above the raft. + coordf_t raft_top = (this->object_config->raft_layers > 0) ? (first_object_layer->print_z - first_object_layer->height) : 0.; +*/ + + // determine contact areas + for (size_t layer_id = 0; layer_id < object->layer_count(); ++ layer_id) { + // 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. + if (m_object_config->raft_layers == 0) { + if (layer_id == 0) + // No raft, 1st object layer cannot be supported by a support contact layer. + continue; + } else if (! this->object_config->support_material) { + // If we are only going to generate raft. Just check the 'overhangs' of the first object layer. + if (layer_id > 0) + break; + } + + const Layer &layer = *object->get_layer(layer_id); + + 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)); + buildplate_only_top_surfaces.insert(buildplate_only_top_surfaces.end(), projection_new.begin(), projection_new.end()); + buildplate_only_top_surfaces = union_(buildplate_only_top_surfaces, false); + } + } + + // detect overhangs and contact areas needed to support them + Polygons overhang_polygons; + Polygons contact_polygons; + if (layer_id == 0) { + // this is the first object layer, so we're here just to get the object + // footprint for the raft + // we only consider contours and discard holes to get a more continuous raft + overhang_polygons = collect_region_slices_outer(layer); + // Extend by SUPPORT_MATERIAL_MARGIN, which is 1.5mm + contact_polygons = offset_(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN)); + } else { + const Layer &lower_layer = *object->get_layer(layer_id-1); + 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. + coord_t fw = layerm.flow(FLOW_ROLE_EXTERNAL_PERIMETER).scaled_width; + Polygons diff_polygons; + + // If a threshold angle was specified, use a different logic for detecting overhangs. + if (threshold_rad > 0. + || layer_id < this->object_config->support_material_enforce_layers + || (this->object_config->raft_layers > 0 && layer_id == 0)) { + coordf_t d = (threshold_rad > 0.) + ? scale_(lower_layer.height * cos(threshold_rad) / sin(threshold_rad)) + : 0.; + + // Shrinking the supported layer by layer_height/atan(threshold_rad). + diff_polygons = diff( + offset_((Polygons)layerm.slices, -d), + (Polygons)lower_layer->slices); + + // only enforce spacing from the object ($fw/2) if the threshold angle + // is not too high: in that case, $d will be very small (as we need to catch + // very short overhangs), and such contact area would be eaten by the + // enforced spacing, resulting in high threshold angles to be almost ignored + if (d > 0.5*fw) + diff_polygons = diff( + offset(diff_polygons, d - 0.5*fw), + (Polygons)lower_layer.slices); + } else { + // Automatic overhang detection. + diff_polygons = diff( + (Polygons)layerm.slices, + offset((Polygons)lower_layer.slices, 0.5*fw)); + + // collapse very tiny spots + diff_polygons = offset2(diff_polygons, -0.1*fw, +0.1*fw); + + // $diff now contains the ring or stripe comprised between the boundary of + // lower slices and the centerline of the last perimeter in this overhanging layer. + // Void $diff means that there's no upper perimeter whose centerline is + // outside the lower slice boundary, thus no overhang + } + + if (this->object_config->dont_support_bridges) { + // compute the area of bridging perimeters + // Note: this is duplicate code from GCode.pm, we need to refactor + + Polygons bridged_perimeters; + { + Flow bridge_flow = layerm.flow(FLOW_ROLE_PERIMETER, true); + + coordf_t nozzle_diameter = this->print_config->get_at('nozzle_diameter', layerm.region->config->perimeter_extruder-1); + 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]; + ExtrusionLoop *loop = dynamic_cast(entity); + overhang_perimeters.push_back(loop ? + loop->polygon->split_at_first_point() : + dynamic_cast(entity)->path); + } + + // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() + for (Polylines::iterator it = overhang_perimeters.begin(); it != overhang_perimeters.end(); ++ it) + (*it)[0].x += 1; + overhang_perimeters = diff(overhang_perimeters, lower_grown_slices); + + // 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 + coordf_t w = std::max(bridge_flow.scaled_width, bridge_flow.scaled_spacing); + for (Polylines::iterator it = overhang_perimeters.begin(); it != overhang_perimeters.end(); ++ it) { + if (it->is_straight()) { + it->extend_start($fw); + it->extend_end($fw); + if (layer.slices.contains(it->find_point()) && layer.slices.contains_point(it->last_point())) + // Offset a polyline into a polygon. + bridged_perimeters.push_back(offset(*it, 0.5 * w + 10.)); + } + } + bridged_perimeters = union(bridged_perimeters); + } + + 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()); + } + + bridged_perimeters.insert(bridged_perimeters.end(), bridges.begin(), bridges.end()); + diff_polygons = diff(diff_polygons, bridged_perimeters, true); + + Polygons bridge_anchors = intersection(offset(polylines, scale_(SUPPORT_MATERIAL_MARGIN)), bridges); + diff_polygons.insert(diff_polygons.end(), bridge_anchors.begin(), bridge_anchors.end()); + } else { + // just remove bridged areas + diff_polygons = diff(diff_polygons, layerm.bridged, true); + } + } // if (this->object_config->dont_support_bridges) + + if (buildplate_only) { + // Don't support overhangs above the top surfaces. + // This step is done before the contact surface is calcuated by growing the overhang region. + diff_polygons = diff(diff_polygons, buildplate_only_top_surfaces); + } + + if (diff_polygons.empty()) + continue; + // NOTE: this is not the full overhang as it misses the outermost half of the perimeter width! + //FIXME Vojtech: Is the preceding comment true? + overhang.polygons.insert(overhang.polygons.end(), diff_polygons.begin(), diff_polygons.end()); + + // 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). + { + Polygons slices_margin = offset((Polygons)lower_layer.slices, 0.5*fw); + if (buildplate_only) { + // Trim the inflated contact surfaces by the top surfaces as well. + slices_margin.insert(slices_margin.end(), buildplate_only_top_surfaces); + slices_margin = union_(slices_margin); + } + for (size_t i = 0; i <= MARGIN_STEPS; ++ i) { + diff_polygons = diff( + offset( + diff_polygons, + (i == 0) ? (0.5 * fw) : (SUPPORT_MATERIAL_MARGIN / MARGIN_STEPS), + CLIPPER_OFFSET_SCALE, + ClipperLib::jtRound, + scale_(0.05) * CLIPPER_OFFSET_SCALE), + slices_margin); + } + } + contact.polygons.insert(contact.polygons.end(), diff_polygons.begin(), diff_polygons.end()); + } + } + + // now apply the contact areas to the layer were they need to be made + if (! contact.polygons.empty()) { + // get the average nozzle diameter used on this layer + Layer &new_layer = layer_allocate(layer_storage); + new_layer.layer_type = sltTopContact; + 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.print_z = layer.print_z - layer.heigh; + 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.; + 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) { + const PrintRegion ®ion = (*it_region_ptr)->region; + nozzle_dmr += m_print_config->get_at('nozzle_diameter', region.config->perimeter_extruder-1); + nozzle_dmr += m_print_config->get_at('nozzle_diameter', region.config->infill_extruder-1); + nozzle_dmr += m_print_config->get_at('nozzle_diameter', region.config->solid_infill_extruder-1); + n_nozzle_dmrs += 3; + } + nozzle_dmr /= n_nozzle_dmrs; + new_layer.print_z = layer.print_z - nozzle_dmr - $self->object_config->support_material_contact_distance; + // 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. + if (new_layer.print_z < this->object_config->get_value('first_layer_height') - EPSILON) + continue; + + new_layer.polygons.swap(contact_polygons); + new_layer.aux_polygons = new Polygons(); + new_layer.aux_polygons->swap(overhang_polygons); + contact_out.push_back(new_layer); + + 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; +} + +/* +static inline LayersPtr sort_layers(LayersSet &layers) +{ + LayersPtr sorted; + for (LayersSet::const_iterator it = layers.begin(); it != layers.end(); ++ it) + sorted.push_back(const_cast(&(*it))); + std::sort(sorted.begin(), sorted.end()); + return sorted; +} +*/ + +void PrintSupportMaterial::bottom_contact_layers(const PrintObject &object, LayersDeque &top_contacts) const +{ + // find object top surfaces + // we'll use them to clip our support and detect where does it stick + LayersPtr bottom_contact_layers; + if (! self->object_config->support_material_buildplate_only && ! layers.empty()) + { + // Sum of unsupported contact areas above the current $layer->print_z. + 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; + // compute projection of the contact areas above this top layer + // first add all the 'new' contact areas to the current projection + // ('new' means all the areas that are lower than the last top layer + // we considered) + // use <= instead of just < because otherwise we'd ignore any contact regions + // having the same Z of top layers + while (contact_idx >= 0 && top_contacts[contact_idx]->print_z > layer.print_z) { + projection.insert(projection.back(), top_contacts[contact_idx]->polygons.begin(), top_contacts[contact_idx]->polygons.end()); + // Now find whether any projection of the contact surfaces above $layer->print_z not yet supported by any top surfaces above $layer->z falls onto this top surface. + // $touching are the contact surfaces supported exclusively by this @top surfaaces. + Polygons touching = intersection(projection, top); + if (touching.empty()) + continue; + // Allocate a new bottom contact layer. + Layer &layer_new = layer_allocate(top_contacts); + bottom_contact_layers.push_back(&layer_new); + layer_new.layer_type = sltBottomContact; + // grow top surfaces so that interface and support generation are generated + // with some spacing from object - it looks we don't need the actual + // top shapes so this can be done here + support_layer.height = m_soluble_interface ? + // Align the interface layer with the object's layer height. + object->get_layer(layer_id + 1)->height : + // Place a bridge flow interface layer over the top surface. + m_interface_flow->nozzle_diameter; + layer_new.print_z = layer.print_z + support_layer.height + m_object_config->support_material_contact_distance; + new_layer.bottom_z = layer.print_z; + layer_new.idx_object_layer_below = layer_id; + layer_new.bridging = true; + Polygons poly_new = offset(touching, m_flow.scaled_width); + 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); + } + } + return bottom_contact_layers; +} + +// 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. +void PrintSupportMaterial::trim_top_contacts_by_bottom_contacts(const PrintObject &object, const LayersPtr &bottom_contacts, const LayersPtr &top_contacts) +{ + size_t idx_top_first = 0; + coordf_t min_layer_height = 0.05; + // For all bottom contact layers: + for (size_t idx_bottom = 0; idx_bottom < bottom_contacts.size() && idx_top_first < top_contacts.size(); ++ idx_bottom) { + const Layer &layer_bottom = *bottom_contacts[idx_bottom]; + // Find the first top layer overlapping with layer_bottom. + while (idx_top_first < top_contacts.size() && top_contacts[idx_top]->print_z <= layer_bottom.bottom_z) + ++ 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) { + Layer &layer_top = *top_contacts[idx_top]; + coordf_t interface_z = m_soluble_interface ? + (layer_top.bottom_z + EPSILON) : + (layer_top.bottom - min_layer_height); + 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; + } + } +} + +PrintSupportMaterial::LayersPtr PrintSupportMaterial::raft_and_intermediate_support_layers( + const PrintObject &object, + const LayersPtr &bottom_contacts, + const LayersPtr &top_contacts, + std::deque &layer_storage, + const coordf_t max_object_layer_height); +{ + // determine layer height for any non-contact layer + // we use max() to prevent many ultra-thin layers to be inserted in case + // layer_height > nozzle_diameter * 0.75 + + //my $nozzle_diameter = $self->print_config->get_at('nozzle_diameter', $self->object_config->support_material_extruder-1); + //my $support_material_height = max($max_object_layer_height, $nozzle_diameter * 0.75); + //my $contact_distance = $self->contact_distance($support_material_height, $nozzle_diameter); + + // Collect all known layers here: + // bottom interface layers, newly created intermediate layers, top interface layers, raft layers. + LayersPtr support_layers; + + coordf_t first_layer_height = m_object_config->get_value('first_layer_height'); + + if (m_object_config->raft_layers == 0) { + // No raft. + } else if (m_object_config->raft_layers == 1) { + // Only the raft interface layer. + } else { + assert(m_object_config->raft_layers > 1); + // Generate the + } + while (z.front()->print_z < first_layer_height - EPSILON) + z.erase(z.begin()); + if (z.empty()) + return z; + + if (z.front()->print_z > first_layer_height + EPSILON) { + Layer support_layer; + support_layer.layer_type = stlFirstLayer; + support_layer.print_z = first_layer_height; + support_layer.height = first_layer_height; + LayersSet::const_iterator it = other.insert(support_layer); + z.insert(z.begin(), support_layer); + } + + // Add raft layers by dividing the space between first layer and first contact layer evenly. + if (m_object_config->raft_layers > 1 && z.size() > 1) { + // z[1] is last raft layer (contact layer for the first layer object) + coordf_t height = (z[1].print_z - z[0].print_z) / (m_object_config->raft_layers - 1); + // Since we already have two raft layers ($z[0] and $z[1]) we need to insert raft_layers-2 more. + z.insert(z.begin() + 1, m_object_config - 2, NULL); + for (size_t i = 1; i < m_object_config->raft_layers; ++ i) { + Layer support_layer; + support_layer.layer_type = stlRaft; + support_layer.print_z = z.front()->print_z + height * i; + support_layer.height = height; + LayersSet::const_iterator it = other.insert(support_layer); + z.insert(z.begin(), support_layer); + } + } + + // create other layers (skip raft layers as they're already done and use thicker layers) + for (int i = int(z.size()); i >= m_object_config->raft_layers; -- i) { + coordf_t target_height = support_material_height; + if (i > 0 && $top{ $z[$i-1] }) { + // Bridge flow? + //FIXME We want to enforce not only the bridge flow height, but also the interface gap! + // This will introduce an additional layer if the gap is set to an extreme value! + $target_height = $nozzle_diameter; + } + + // enforce first layer height + //FIXME better to split the layers regularly, than to bite a constant height one at a time, + // and then be left with a very thin layer at the end. + if ((i == 0 && z[i] > target_height + first_layer_height) + || (z[i] - z[i-1] > target_height + EPSILON)) { + splice @z, $i, 0, ($z[$i] - $target_height); + $i++; + } + } + + // Collect and sort the extremes (bottoms of the top contacts and tops of the bottom contacts). + std::vector extremes; + extremes.reserve(top_contacts.size() + bottom_contacts.size()); + for (size_t i = 0; i < top_contacts.size(); ++ i) + extremes.push_back(LayerExtreme(top_contacts[i], false)); + for (size_t i = 0; i < bottom_contacts.size(); ++ i) + extremes.push_back(LayerExtreme(bottom_contacts[i], true)); + std::sort(extremes.begin(), extremes.end()); + + // Generate intermediate layers. + LayersPtr intermediate_layers; + coordf_t max_support_layer_height; + for (size_t idx_extreme = 0; idx_extreme + 1 < extremes.size(); ++ idx_extreme) { + LayerExtreme &extr1 = extremes[idx_extreme]; + LayerExtreme &extr2 = extremes[idx_extreme+1]; + coordf_t dist = extr2.z() - extr1.z(); + assert(dist > 0.); + // Insert intermediate layers. + size_t n_layers_extra = size_t(ceil(dist / max_support_layer_height)); + coordf_t step = dist / coordf_t(n_layers_extra); + if (! m_soluble_interface && extr2.layer->layer_type == stlTop) { + // This is a top interface layer, which does not have a height assigned yet. Do it now. + extr2.layer->height = step; + extr2.layer->bottom_z = extr2.layer->top_z - step; + -- n_layers_extra; + } + for (size_t i = 0; i < n_layers_extra; ++ i) { + Layer &layer_new = layer_allocate(layer_storage); + layer_new.layer_type = sltIntermediate; + layer_new.height = step; + layer_new.bottom_z = extr1.z() + i * step; + layer_new.top_z = layer_new.bottom_z + step; + intermediate_layers.push_back(&layer_new); + } + } + + return support_layers; +} + +inline void polygons_append(Polygons &dst, const Polygons &src) +{ + dst.insert(dst.end(), src.begin(), src.end()); +} + +// At this stage there shall be intermediate_layers allocated between bottom_contacts and top_contacts, but they have no polygons assigned. +// Also the bottom/top_contacts shall have a thickness assigned. +void PrintSupportMaterial::generate_base_layers( + const PrintObject &object, + const LayersPtr &bottom_contacts, + const LayersPtr &top_contacts, + LayersPtr &intermediate_layers) +{ + 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); + + 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) + { + Layer &layer_intermediate = intermediate_layers[idx_intermediate]; + + // 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. + while (idx_top_contact_above >= 0 && top_contacts[idx_top_contact_above].bottom_z > layer_intermediate.print_z + EPSILON) + -- idx_top_contact_above; + if (idx_top_contact_above >= 0 && top_contacts[idx_top_contact_above].top_z > layer_intermediate.print_z) + polygons_append(polygons_new, top_contacts[idx_top_contact_above].polygons); + + // Add polygons from the intermediate layer above. + if (idx_intermediate + 1 < int(intermediate_layers.size())) + polygons_append(polygons_new, intermediate_layers[idx_intermediate+1].polygons); + + // Polygons to trim polygons_new. + Polygons polygons_trimming; + + // Find the first top_contact layer intersecting with this layer. + while (idx_top_contact_overlapping >= 0 && top_contacts[idx_top_contact_overlapping].bottom_z > layer_intermediate.print_z + overlap_extra_above - EPSILON) + -- idx_top_contact_overlapping; + // Collect all the top_contact layer intersecting with this layer. + for (int i = idx_top_contact_overlapping; i >= 0; -- i) { + Layer &layer_top_overlapping = top_contacts[idx_top_contact_overlapping]; + 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. + while (idx_bottom_contact_overlapping >= 0 && bottom_contacts[idx_bottom_contact_overlapping].bottom_z > layer_intermediate.print_z + overlap_extra_above - EPSILON) + -- idx_bottom_contact_overlapping; + // Collect all the top_contact layer intersecting with this layer. + for (int i = idx_bottom_contact_overlapping; i >= 0; -- i) { + Layer &layer_bottom_overlapping = bottom_contacts[idx_bottom_contact_overlapping]; + 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; + const coordf_t gap_xy_scaled = m_flow.scaled_width; + size_t idx_object_layer_overlapping = 0; + // For all intermediate layers: + for (LayersPtr::iterator it_layer = intermediate_layers.begin(); it_layer != intermediate_layers.end(); ++ it_layer) { + Layer &layer_intermediate = *(*it_layer); + 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) { + Layer &object_layer = *object.get_layer(i); + 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, + offset(polygons_trimming, gap_xy_scaled), + ); + } +} + +// Convert some of the intermediate layers into top/bottom interface layers. +LayersPtr PrintSupportMaterial::generate_interface_layers( + const PrintObject &object, + const LayersPtr &bottom_contacts, + const LayersPtr &top_contacts, + LayersPtr &intermediate_layers, + std::deque &layer_storage) +{ + // 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; + + LayersPtr interface_layers; + // 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) { + Layer &intermediate_layer = intermediate_layers[idx_intermediate_layer]; + // 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(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(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. + while (idx_top_contact_first < top_contacts.size() && top_contacts[idx_top_contact_first]->print_z < intermediate_layer->print_z) + ++ 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) { + const Layer &top_contact_layer = top_contacts[idx_top_contact]; + 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) { + const Layer &bottom_contact_layer = bottom_contacts[idx_bottom_contact]; + if (bottom_contact_layer.top_z - EPSILON > intermediate_layer.bottom_z) + 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. + Layer &layer_new = layer_allocate(layer_storage); + layer_new.layer_type = polygons_top_contact_projected.empty() ? stlBottomInterface : sltTopInterface; + 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; + top_interface_layers.push_back(layer_new); + + polygons_append(polygons_top_contact_projected, polygons_bottom_contact_projected); + polygons_top_contact_projected = union_(polygons_top_contact_projected, true); + layer_new.polygons = intersection(intermediate_layers.polygons, polygons_top_contact_projected); + //FIXME filter layer_new.polygons islands by a minimum area? +// $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; + intermediate_layers.polygons = diff(intermediate_layers.polygons, polygons_top_contact_projected, false) + } + } + + return interface_layers; +} + +void PrintSupportMaterial::generate_toolpaths( + const PrintObject &object, + const MyLayersPtr &bottom_contacts, + const MyLayersPtr &top_contacts, + const MyLayersPtr &intermediate_layers, + const MyLayersPtr &interface_layers) +{ + // Shape of the top contact area. + int n_contact_loops = 1; + coordf_t circle_radius = 1.5 * m_interface_flow.scaled_width; + coordf_t circle_distance = 3. * circle_radius; + Polygon circle; + circle.reserve(6); + for (size_t i = 0; i < 6; ++ i) { + double angle = double(i) * m_PI / 3.; + circle.push_back(Point(circle_radius * cos(angle), circle_radius * sin(angle))); + } + +// 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 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 filler_interface = std::auto_ptr(Fill::new_from_type(ipRectilinear)); + std::auto_ptr filler_support = std::auto_ptr(Fill::new_from_type(infill_pattern)); + { + BoundingBox bbox_object = object.bounding_box(); + fill_interface->set_bounding_box(bbox_object); + fill_interface->set_bounding_box(fill_support); + } + + coordf_t interface_angle = m_object_config->support_material_angle + 90.; + coordf_t interface_spacing = m_object_config->support_material_interface_spacing + m_interface_flow.spacing; + coordf_t interface_density = (interface_spacing == 0.) ? 1. : (m_interface_flow.spacing / m_interface_spacing); + coordf_t support_spacing = m_object_config.object_config->support_material_spacing + m_flow.spacing; + coordf_t support_density = (support_spacing == 0.) ? 1. : (m_flow.spacing / support_spacing); + + //FIXME Parallelize the support generator: + /* + Slic3r::parallelize( + threads => $self->print_config->threads, + items => [ 0 .. n_$object->support_layers} ], + thread_cb => sub { + my $q = shift; + while (defined (my $layer_id = $q->dequeue)) { + $process_layer->($layer_id); + } + }, + no_threads_cb => sub { + $process_layer->($_) for 0 .. n_{$object->support_layers}; + }, + ); + */ + // 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; + Polygons interface_polygons; + Polygons base_polygons; + // Increment the layer indices to find a layer at support_layer.print_z. + 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; ++ interface_layers ) ; + // Copy polygons from the layers. + 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_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; + + // We redefine flows locally by applying this layer's height. + Flow flow = m_flow; + Flow interface_flow = m_interface_flow; + flow.set_height(layer.height); + interface_flow.set_height(layer.height); + + /* + 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; + if (idx_layer_top_contact < top_contacts.size() && top_contacts[idx_layer_top_contact].print_z < support_layer.print_z + EPSILON) + { + // Having a top interface layer. + Polygons top_contact_polygons = top_contacts[idx_layer_top_contact].polygons; + 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) + top_contact_polygons = offset(top_contact_polygons, - 0.5 * interface_flow.scaled_width); + + 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; + { + 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) + if (! intersection_pl(it_contact->split_at_first_point, overhang_with_margin).empty()) { + 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(); + for (size_t i = 0; i < circle_new.size(); ++ i) + circle_new[i].translate(*it_center); + } + } + } + // Apply a pattern to the loop. + @loops0 = diff(external_loops, circles); + } + + // make more loops + Polygons loops = loops0; + for (size_t i = 1; i < n_contact_loops) + polygons_append(loops, offset2(loops0, - 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 + { + Polylines loop_lines; + loop_lines.reserve(loops.size()); + for (Polygons::const_iterator it = loops.begin(); it != loops.end(); ++ it) + loop_lines.push_back(it->split_at_first_point); + loops = intersection_pl(loop_lines, offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); + } + + // 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 + contact_infill_polygons = diff(top_contact_polygons, offset(loops, circle_radius * 1.1, true)); + + // Transform loops into ExtrusionPath objects. + for (Polylines::const_iterator it_polyline = loops.begin(); it_polyline != loops.end(); ++ it_polyline) { + ExtrusionPath *extrusion_path = new ExtrusionPath(erSupportMaterialInterface); + support_layer.support_interface_fills.entities.push_back(extrusion_path); + extrusion_path->polyline = it_polyline->split_at_first_point; + extrusion_path->mm3_per_mm = interface_flow.mm3_per_mm; + 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. + filler_interface->set_angle(interface_angle); + filler_interface->set_spacing(interface_flow.spacing); + + // find centerline of the external loop + interface_polygons = offset2(interface_polygons, SCALED_EPSILON, - SCALED_EPSILON - 0.5 * interface_flow.scaled_width); + // 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) { + if (it_polygon->is_clockwise) { + 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()); + interface_polygons_new.back().swap(*it_polygon); + } + 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) { + FillParams fill_params; + fill_params.density = interface_density; + fill_params.complete = true; + Polylines polylines = filler_interface->fill_surface(Surface(stInternal, *it_expolygon), fill_params); + 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); + extrusion_path->polyline = it_polyline->split_at_first_point; + extrusion_path->mm3_per_mm = interface_flow.mm3_per_mm; + 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(); + filler->set_angle(angles[support_layer_id % angles.size()]); + // We don't use $base_flow->spacing because we need a constant spacing + // value that guarantees that all layers are correctly aligned. + filler->set_spacing(flow.spacing); + + coordf_t density = support_density; + Flow base_flow = flow; + + // find centerline of the external loop/extrusions + ExPolygons to_infill = offset2_ex(base_polygons, SCALED_EPSILON, - SCALED_EPSILON - 0.5*flow.scaled_width); + + /* + 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(); + filler->set_angle(m_object_config->support_material_angle + 90.); + 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. + filler->set_spacing(base_flow.spacing); + } 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 + Polygons to_infill_polygons = (Polygons)to_infill; + 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); + extrusion_path->polyline = it_polyline->split_at_first_point; + extrusion_path->mm3_per_mm = flow.mm3_per_mm; + extrusion_path->width = flow.width; + extrusion_path->height = support_layer.height; + } + // TODO: use offset2_ex() + to_infill = offset_ex(to_infill_polygons, - flow.scaled_spacing); + } + + for (ExPolygons::const_iterator it_expolygon = to_infill.begin(); it_expolygon != to_infill.end(); ++ it_expolygon) { + FillParams fill_params; + fill_params.density = density; + fill_params.complete = true; + Polylines polylines = filler->fill_surface(Surface(stInternal, *it_expolygon), fill_params); + 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); + extrusion_path->polyline = it_polyline->split_at_first_point; + extrusion_path->mm3_per_mm = base_flow.mm3_per_mm; + extrusion_path->width = base_flow.width; + extrusion_path->height = support_layer.height; + } + } + + // 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(); + filler->set_angle(angles[support_layer_id % angles.size()]); + // We don't use $base_flow->spacing because we need a constant spacing + // value that guarantees that all layers are correctly aligned. + filler->set_spacing(flow.spacing); + + coordf_t density = support_density; + Flow base_flow = flow; + + // find centerline of the external loop/extrusions + ExPolygons to_infill = offset2_ex(base_polygons, SCALED_EPSILON, - SCALED_EPSILON - 0.5*flow.scaled_width); + + /* + 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(); + filler->set_angle(m_object_config->support_material_angle + 90.); + 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. + filler->set_spacing(base_flow.spacing); + } 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 + Polygons to_infill_polygons = (Polygons)to_infill; + 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); + extrusion_path->polyline = it_polyline->split_at_first_point; + extrusion_path->mm3_per_mm = flow.mm3_per_mm; + extrusion_path->width = flow.width; + extrusion_path->height = support_layer.height; + } + // TODO: use offset2_ex() + to_infill = offset_ex(to_infill_polygons, - flow.scaled_spacing); + } + + for (ExPolygons::const_iterator it_expolygon = to_infill.begin(); it_expolygon != to_infill.end(); ++ it_expolygon) { + FillParams fill_params; + fill_params.density = density; + fill_params.complete = true; + Polylines polylines = filler->fill_surface(Surface(stInternal, *it_expolygon), fill_params); + 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); + extrusion_path->polyline = it_polyline->split_at_first_point; + extrusion_path->mm3_per_mm = base_flow.mm3_per_mm; + extrusion_path->width = base_flow.width; + extrusion_path->height = support_layer.height; + } + } + + /* + 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} ], + ); + } + */ + }; +} + +/* +void PrintSupportMaterial::clip_by_pillars( + 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 diff --git a/xs/src/libslic3r/SupportMaterial.hpp b/xs/src/libslic3r/SupportMaterial.hpp index edea22695..d1343e4f2 100644 --- a/xs/src/libslic3r/SupportMaterial.hpp +++ b/xs/src/libslic3r/SupportMaterial.hpp @@ -4,8 +4,191 @@ namespace Slic3r { // how much we extend support around the actual contact area -#define SUPPORT_MATERIAL_MARGIN 1.5 +#define SUPPORT_MATERIAL_MARGIN 1.5 -} +// Instantiated by Slic3r::Print::Object->_support_material() +class PrintSupportMaterial +{ +public: + enum SupporLayerType { + sltUnknown = 0, + sltRaft, + stlFirstLayer, + sltBottomContact, + sltBottomInterface, + sltBase, + sltTopInterface, + sltTopContact, + // Some undecided type yet. It will turn into stlBase first, then it may turn into stlBottomInterface or stlTopInterface. + stlIntermediate, + }; + + class MyLayer + { + public: + MyLayer() : + layer_type(sltUnknown), + print_z(0.), + bottom_z(0.), + height(0.), + idx_object_layer_above(size_t(-1)), + idx_object_layer_below(size_t(-1)), + bridging(false) + {} + + ~MyLayer() + { + delete aux_polygons; + aux_polygons = NULL; + } + + bool operator==(const MyLayer &layer2) const { + return print_z == layer2.printz && height == layer2.height && bridging == layer2.bridging; + } + + bool operator<(const MyLayer &layer2) const { + if (print_z < layer2.print_z) { + return true; + } else if (print_z == layer2.print_z) { + if (height > layer2.height) + return true; + else if (height == layer2.height) { + return bridging < layer2.bridging; + } else + return false; + } else + return false; + } + + SupporLayerType layer_type; + // Z used for printing in unscaled coordinates + coordf_t print_z; + // Bottom height of this layer. For soluble layers, bottom_z + height = print_z, + // otherwise bottom_z + gap + height = print_z. + coordf_t bottom_z; + // layer height in unscaled coordinates + coordf_t height; + // Index of a PrintObject layer_id supported by this layer. This will be set for top contact layers. + // If this is not a contact layer, it will be set to size_t(-1). + size_t idx_object_layer_above; + // Index of a PrintObject layer_id, which supports this layer. This will be set for bottom contact layers. + // If this is not a contact layer, it will be set to size_t(-1). + size_t idx_object_layer_below; + // Use a bridging flow when printing this support layer. + bool bridging; + + // Polygons to be filled by the support pattern. + Polygons polygons; + // Currently for the contact layers only: Overhangs are stored here. + Polygons *aux_polygons; + }; + + struct LayerExtreme + { + LayerExtreme(MyLayer *alayer, bool ais_top) : layer(alayer), is_top(ais_top) {} + MyLayer *layer; + // top or bottom extreme + bool is_top; + + coordf_t z() const { return is_top ? layer->print_z : layer->print_z - height; } + + bool operator<(const LayerExtreme &other) const { return z() < other.z(); } + } + + struct LayerPrintZ_Hash { + static size_t operator(const MyLayer &layer) { + return std::hash(layer.print_z)^std::hash(layer.height)^size_t(layer.bridging); + } + }; + + typedef std::set MyLayersSet; + typedef std::vector MyLayersPtr; + typedef std::deque MyLayersDeque; + typedef std::deque MyLayerStorage; + +public: + PrintSupportMaterial() : + m_object(NULL), + m_print_config(NULL), + m_object_config(NULL), + m_soluble_interface(false), + m_support_layer_height_max(0.), + m_support_interface_layer_height_max(0.) + {} + + void setup( + const PrintConfig *print_config; + const ObjectConfig *object_config; + Flow flow; + Flow first_layer_flow; + Flow interface_flow; + bool soluble_interface) + { + this->m_object = object; + this->m_print_config = print_config; + this->m_object_config = object_config; + this->m_flow = flow; + this->m_first_layer_flow = first_layer_flow; + this->m_interface_flow = interface_flow; + this->m_soluble_interface = soluble_interface; + } + + void generate(const PrintObject *object); + +private: + // Generate top contact layers supporting overhangs. + // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. + // If supports over bed surface only are requested, don't generate contact layers over an object. + MyLayersPtr top_contact_layers(const PrintObject &object, MyLayerStorage &layer_storage) const; + + // Generate bottom contact layers supporting the top contact layers. + // For a soluble interface material synchronize the layer heights with the object, + // otherwise set the layer height to a bridging flow of a support interface nozzle. + MyLayersPtr bottom_contact_layers(const PrintObject &object, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage) const; + + // Generate raft layers and the intermediate support layers between the bottom contact and top contact surfaces. + MyLayersPtr raft_and_intermediate_support_layers( + const PrintObject &object, + const MyLayersPtr &bottom_contacts, + const MyLayersPtr &top_contacts, + MyLayerStorage &layer_storage, + const coordf_t max_object_layer_height); + + void generate_base_layers( + const PrintObject &object, + const MyLayersPtr &bottom_contacts, + const MyLayersPtr &top_contacts, + MyLayersPtr &intermediate_layers); + + MyLayersPtr generate_interface_layers( + const PrintObject &object, + const MyLayersPtr &bottom_contacts, + const MyLayersPtr &top_contacts, + MyLayersPtr &intermediate_layers, + MyLayerStorage &layer_storage); + +/* + void generate_pillars_shape(); + void clip_with_shape(); +*/ + + // Produce the actual G-code. + void generate_toolpaths( + const PrintObject &object, + const MyLayersPtr &bottom_contacts, + const MyLayersPtr &top_contacts, + const MyLayersPtr &intermediate_layers, + const MyLayersPtr &interface_layers); + + const PrintConfig *m_print_config; + const ObjectConfig *m_object_config; + Flow m_flow; + Flow m_first_layer_flow; + Flow m_interface_flow; + bool m_soluble_interface; + + coordf_t m_support_layer_height_max; + coordf_t m_support_interface_layer_height_max; +}; #endif diff --git a/xs/src/libslic3r/Surface.hpp b/xs/src/libslic3r/Surface.hpp index 7f2c09dab..6bed96a0c 100644 --- a/xs/src/libslic3r/Surface.hpp +++ b/xs/src/libslic3r/Surface.hpp @@ -6,11 +6,29 @@ namespace Slic3r { -enum SurfaceType { stTop, stBottom, stBottomBridge, stInternal, stInternalSolid, stInternalBridge, stInternalVoid, stPerimeter }; +enum SurfaceType { + // Top horizontal surface, visible from the top. + stTop, + // Bottom horizontal surface, visible from the bottom, printed with a normal extrusion flow. + stBottom, + // Bottom horizontal surface, visible from the bottom, unsupported, printed with a bridging extrusion flow. + stBottomBridge, + // Normal sparse infill. + stInternal, + // Full infill, supporting the top surfaces and/or defining the verticall wall thickness. + stInternalSolid, + // 1st layer of dense infill over sparse infill, printed with a bridging extrusion flow. + stInternalBridge, + // stInternal turns into void surfaces if the sparse infill is used for supports only, + // or if sparse infill layers get combined into a single layer. + stInternalVoid, + // Inner/outer perimeters. + stPerimeter +}; class Surface { - public: +public: SurfaceType surface_type; ExPolygon expolygon; double thickness; // in mm