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