Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/libslic3r/Print.cpp')
-rw-r--r--src/libslic3r/Print.cpp1370
1 files changed, 1370 insertions, 0 deletions
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
new file mode 100644
index 000000000..cdc12d2d1
--- /dev/null
+++ b/src/libslic3r/Print.cpp
@@ -0,0 +1,1370 @@
+#include "Print.hpp"
+#include "BoundingBox.hpp"
+#include "ClipperUtils.hpp"
+#include "Extruder.hpp"
+#include "Flow.hpp"
+#include "Geometry.hpp"
+#include "I18N.hpp"
+#include "SupportMaterial.hpp"
+#include "GCode.hpp"
+#include "GCode/WipeTowerPrusaMM.hpp"
+#include <algorithm>
+#include <unordered_set>
+#include <boost/filesystem.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/log/trivial.hpp>
+
+#include "PrintExport.hpp"
+
+//! macro used to mark string used at localization,
+//! return same string
+#define L(s) Slic3r::I18N::translate(s)
+
+namespace Slic3r {
+
+template class PrintState<PrintStep, psCount>;
+template class PrintState<PrintObjectStep, posCount>;
+
+void Print::clear_objects()
+{
+ tbb::mutex::scoped_lock lock(m_mutex);
+ for (PrintObject *object : m_objects)
+ delete object;
+ m_objects.clear();
+ for (PrintRegion *region : m_regions)
+ delete region;
+ m_regions.clear();
+ this->invalidate_all_steps();
+}
+
+void Print::delete_object(size_t idx)
+{
+ tbb::mutex::scoped_lock lock(m_mutex);
+ // destroy object and remove it from our container
+ delete m_objects[idx];
+ m_objects.erase(m_objects.begin() + idx);
+ this->invalidate_all_steps();
+ // TODO: purge unused regions
+}
+
+void Print::reload_object(size_t /* idx */)
+{
+ ModelObjectPtrs model_objects;
+ {
+ tbb::mutex::scoped_lock lock(m_mutex);
+ /* TODO: this method should check whether the per-object config and per-material configs
+ have changed in such a way that regions need to be rearranged or we can just apply
+ the diff and invalidate something. Same logic as apply_config()
+ For now we just re-add all objects since we haven't implemented this incremental logic yet.
+ This should also check whether object volumes (parts) have changed. */
+ // collect all current model objects
+ model_objects.reserve(m_objects.size());
+ for (PrintObject *object : m_objects)
+ model_objects.push_back(object->model_object());
+ // remove our print objects
+ for (PrintObject *object : m_objects)
+ delete object;
+ m_objects.clear();
+ for (PrintRegion *region : m_regions)
+ delete region;
+ m_regions.clear();
+ this->invalidate_all_steps();
+ }
+ // re-add model objects
+ for (ModelObject *mo : model_objects)
+ this->add_model_object(mo);
+}
+
+// Reloads the model instances into the print class.
+// The slicing shall not be running as the modified model instances at the print
+// are used for the brim & skirt calculation.
+// Returns true if the brim or skirt have been invalidated.
+bool Print::reload_model_instances()
+{
+ tbb::mutex::scoped_lock lock(m_mutex);
+ bool invalidated = false;
+ for (PrintObject *object : m_objects)
+ invalidated |= object->reload_model_instances();
+ return invalidated;
+}
+
+PrintObjectPtrs Print::get_printable_objects() const
+{
+ PrintObjectPtrs printable_objects(m_objects);
+ printable_objects.erase(std::remove_if(printable_objects.begin(), printable_objects.end(), [](PrintObject* o) { return !o->is_printable(); }), printable_objects.end());
+ return printable_objects;
+}
+
+PrintRegion* Print::add_region()
+{
+ m_regions.emplace_back(new PrintRegion(this));
+ return m_regions.back();
+}
+
+PrintRegion* Print::add_region(const PrintRegionConfig &config)
+{
+ m_regions.emplace_back(new PrintRegion(this, config));
+ return m_regions.back();
+}
+
+// Called by Print::apply_config().
+// This method only accepts PrintConfig option keys.
+bool Print::invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys)
+{
+ if (opt_keys.empty())
+ return false;
+
+ // Cache the plenty of parameters, which influence the G-code generator only,
+ // or they are only notes not influencing the generated G-code.
+ static std::unordered_set<std::string> steps_gcode = {
+ "avoid_crossing_perimeters",
+ "bed_shape",
+ "bed_temperature",
+ "before_layer_gcode",
+ "between_objects_gcode",
+ "bridge_acceleration",
+ "bridge_fan_speed",
+ "cooling",
+ "default_acceleration",
+ "deretract_speed",
+ "disable_fan_first_layers",
+ "duplicate_distance",
+ "end_gcode",
+ "end_filament_gcode",
+ "extrusion_axis",
+ "extruder_clearance_height",
+ "extruder_clearance_radius",
+ "extruder_colour",
+ "extruder_offset",
+ "extrusion_multiplier",
+ "fan_always_on",
+ "fan_below_layer_time",
+ "filament_colour",
+ "filament_diameter",
+ "filament_density",
+ "filament_notes",
+ "filament_cost",
+ "filament_max_volumetric_speed",
+ "first_layer_acceleration",
+ "first_layer_bed_temperature",
+ "first_layer_speed",
+ "gcode_comments",
+ "gcode_flavor",
+ "infill_acceleration",
+ "layer_gcode",
+ "min_fan_speed",
+ "max_fan_speed",
+ "max_print_height",
+ "min_print_speed",
+ "max_print_speed",
+ "max_volumetric_speed",
+ "max_volumetric_extrusion_rate_slope_positive",
+ "max_volumetric_extrusion_rate_slope_negative",
+ "notes",
+ "only_retract_when_crossing_perimeters",
+ "output_filename_format",
+ "perimeter_acceleration",
+ "post_process",
+ "printer_notes",
+ "retract_before_travel",
+ "retract_before_wipe",
+ "retract_layer_change",
+ "retract_length",
+ "retract_length_toolchange",
+ "retract_lift",
+ "retract_lift_above",
+ "retract_lift_below",
+ "retract_restart_extra",
+ "retract_restart_extra_toolchange",
+ "retract_speed",
+ "single_extruder_multi_material_priming",
+ "slowdown_below_layer_time",
+ "standby_temperature_delta",
+ "start_gcode",
+ "start_filament_gcode",
+ "toolchange_gcode",
+ "threads",
+ "travel_speed",
+ "use_firmware_retraction",
+ "use_relative_e_distances",
+ "use_volumetric_e",
+ "variable_layer_height",
+ "wipe",
+ "wipe_tower_x",
+ "wipe_tower_y",
+ "wipe_tower_rotation_angle"
+ };
+
+ static std::unordered_set<std::string> steps_ignore;
+
+ std::vector<PrintStep> steps;
+ std::vector<PrintObjectStep> osteps;
+ bool invalidated = false;
+
+ for (const t_config_option_key &opt_key : opt_keys) {
+ if (steps_gcode.find(opt_key) != steps_gcode.end()) {
+ // These options only affect G-code export or they are just notes without influence on the generated G-code,
+ // so there is nothing to invalidate.
+ steps.emplace_back(psGCodeExport);
+ } else if (steps_ignore.find(opt_key) != steps_ignore.end()) {
+ // These steps have no influence on the G-code whatsoever. Just ignore them.
+ } else if (
+ opt_key == "skirts"
+ || opt_key == "skirt_height"
+ || opt_key == "skirt_distance"
+ || opt_key == "min_skirt_length"
+ || opt_key == "ooze_prevention") {
+ steps.emplace_back(psSkirt);
+ } else if (opt_key == "brim_width") {
+ steps.emplace_back(psBrim);
+ steps.emplace_back(psSkirt);
+ } else if (
+ opt_key == "nozzle_diameter"
+ || opt_key == "resolution") {
+ osteps.emplace_back(posSlice);
+ } else if (
+ opt_key == "complete_objects"
+ || opt_key == "filament_type"
+ || opt_key == "filament_soluble"
+ || opt_key == "first_layer_temperature"
+ || opt_key == "filament_loading_speed"
+ || opt_key == "filament_loading_speed_start"
+ || opt_key == "filament_unloading_speed"
+ || opt_key == "filament_unloading_speed_start"
+ || opt_key == "filament_toolchange_delay"
+ || opt_key == "filament_cooling_moves"
+ || opt_key == "filament_minimal_purge_on_wipe_tower"
+ || opt_key == "filament_cooling_initial_speed"
+ || opt_key == "filament_cooling_final_speed"
+ || opt_key == "filament_ramming_parameters"
+ || opt_key == "gcode_flavor"
+ || opt_key == "infill_first"
+ || opt_key == "single_extruder_multi_material"
+ || opt_key == "spiral_vase"
+ || opt_key == "temperature"
+ || opt_key == "wipe_tower"
+ || opt_key == "wipe_tower_width"
+ || opt_key == "wipe_tower_bridging"
+ || opt_key == "wiping_volumes_matrix"
+ || opt_key == "parking_pos_retraction"
+ || opt_key == "cooling_tube_retraction"
+ || opt_key == "cooling_tube_length"
+ || opt_key == "extra_loading_move"
+ || opt_key == "z_offset") {
+ steps.emplace_back(psWipeTower);
+ } else if (
+ opt_key == "first_layer_extrusion_width"
+ || opt_key == "min_layer_height"
+ || opt_key == "max_layer_height") {
+ osteps.emplace_back(posPerimeters);
+ osteps.emplace_back(posInfill);
+ osteps.emplace_back(posSupportMaterial);
+ steps.emplace_back(psSkirt);
+ steps.emplace_back(psBrim);
+ } else {
+ // for legacy, if we can't handle this option let's invalidate all steps
+ //FIXME invalidate all steps of all objects as well?
+ invalidated |= this->invalidate_all_steps();
+ // Continue with the other opt_keys to possibly invalidate any object specific steps.
+ }
+ }
+
+ sort_remove_duplicates(steps);
+ for (PrintStep step : steps)
+ invalidated |= this->invalidate_step(step);
+ sort_remove_duplicates(osteps);
+ for (PrintObjectStep ostep : osteps)
+ for (PrintObject *object : m_objects)
+ invalidated |= object->invalidate_step(ostep);
+ return invalidated;
+}
+
+bool Print::invalidate_step(PrintStep step)
+{
+ bool invalidated = m_state.invalidate(step, m_mutex, m_cancel_callback);
+ // Propagate to dependent steps.
+ //FIXME Why should skirt invalidate brim? Shouldn't it be vice versa?
+ if (step == psSkirt)
+ invalidated |= m_state.invalidate(psBrim, m_mutex, m_cancel_callback);
+ return invalidated;
+}
+
+// returns true if an object step is done on all objects
+// and there's at least one object
+bool Print::is_step_done(PrintObjectStep step) const
+{
+ if (m_objects.empty())
+ return false;
+ for (const PrintObject *object : m_objects)
+ if (!object->m_state.is_done(step))
+ return false;
+ return true;
+}
+
+// returns 0-based indices of used extruders
+std::vector<unsigned int> Print::object_extruders() const
+{
+ std::vector<unsigned int> extruders;
+
+ for (PrintRegion* region : m_regions) {
+ // these checks reflect the same logic used in the GUI for enabling/disabling
+ // extruder selection fields
+ if (region->config().perimeters.value > 0 || m_config.brim_width.value > 0)
+ extruders.push_back(region->config().perimeter_extruder - 1);
+ if (region->config().fill_density.value > 0)
+ extruders.push_back(region->config().infill_extruder - 1);
+ if (region->config().top_solid_layers.value > 0 || region->config().bottom_solid_layers.value > 0)
+ extruders.push_back(region->config().solid_infill_extruder - 1);
+ }
+
+ sort_remove_duplicates(extruders);
+ return extruders;
+}
+
+// returns 0-based indices of used extruders
+std::vector<unsigned int> Print::support_material_extruders() const
+{
+ std::vector<unsigned int> extruders;
+ bool support_uses_current_extruder = false;
+
+ for (PrintObject *object : m_objects) {
+ if (object->has_support_material()) {
+ if (object->config().support_material_extruder == 0)
+ support_uses_current_extruder = true;
+ else
+ extruders.push_back(object->config().support_material_extruder - 1);
+ if (object->config().support_material_interface_extruder == 0)
+ support_uses_current_extruder = true;
+ else
+ extruders.push_back(object->config().support_material_interface_extruder - 1);
+ }
+ }
+
+ if (support_uses_current_extruder)
+ // Add all object extruders to the support extruders as it is not know which one will be used to print supports.
+ append(extruders, this->object_extruders());
+
+ sort_remove_duplicates(extruders);
+ return extruders;
+}
+
+// returns 0-based indices of used extruders
+std::vector<unsigned int> Print::extruders() const
+{
+ std::vector<unsigned int> extruders = this->object_extruders();
+ append(extruders, this->support_material_extruders());
+ sort_remove_duplicates(extruders);
+ return extruders;
+}
+
+void Print::_simplify_slices(double distance)
+{
+ for (PrintObject *object : m_objects) {
+ for (Layer *layer : object->m_layers) {
+ layer->slices.simplify(distance);
+ for (LayerRegion *layerm : layer->regions())
+ layerm->slices.simplify(distance);
+ }
+ }
+}
+
+double Print::max_allowed_layer_height() const
+{
+ double nozzle_diameter_max = 0.;
+ for (unsigned int extruder_id : this->extruders())
+ nozzle_diameter_max = std::max(nozzle_diameter_max, m_config.nozzle_diameter.get_at(extruder_id));
+ return nozzle_diameter_max;
+}
+
+// Caller is responsible for supplying models whose objects don't collide
+// and have explicit instance positions.
+void Print::add_model_object(ModelObject* model_object, int idx)
+{
+ tbb::mutex::scoped_lock lock(m_mutex);
+ // Initialize a new print object and store it at the given position.
+ PrintObject *object = new PrintObject(this, model_object, model_object->raw_bounding_box());
+ if (idx != -1) {
+ delete m_objects[idx];
+ m_objects[idx] = object;
+ } else
+ m_objects.emplace_back(object);
+ // Invalidate all print steps.
+ //FIXME lock mutex!
+ this->invalidate_all_steps();
+
+ size_t volume_id = 0;
+ for (const ModelVolume *volume : model_object->volumes) {
+ if (! volume->is_model_part() && ! volume->is_modifier())
+ continue;
+ // Get the config applied to this volume.
+ PrintRegionConfig config = this->_region_config_from_model_volume(*volume);
+ // Find an existing print region with the same config.
+ size_t region_id = size_t(-1);
+ for (size_t i = 0; i < m_regions.size(); ++ i)
+ if (config.equals(m_regions[i]->config())) {
+ region_id = i;
+ break;
+ }
+ // If no region exists with the same config, create a new one.
+ if (region_id == size_t(-1)) {
+ region_id = m_regions.size();
+ this->add_region(config);
+ }
+ // Assign volume to a region.
+ object->add_region_volume(region_id, volume_id);
+ ++ volume_id;
+ }
+
+ // Apply config to print object.
+ object->config_apply(this->default_object_config());
+ {
+ //normalize_and_apply_config(object->config(), model_object->config);
+ DynamicPrintConfig src_normalized(model_object->config);
+ src_normalized.normalize();
+ object->config_apply(src_normalized, true);
+ }
+
+ // update placeholders
+ {
+ // get the first input file name
+ std::string input_file;
+ std::vector<std::string> v_scale;
+ for (const PrintObject *object : m_objects) {
+ const ModelObject &mobj = *object->model_object();
+ v_scale.push_back(boost::lexical_cast<std::string>(mobj.instances[0]->scaling_factor*100) + "%");
+ if (input_file.empty())
+ input_file = mobj.input_file;
+ }
+
+ PlaceholderParser &pp = m_placeholder_parser;
+ pp.set("scale", v_scale);
+ if (! input_file.empty()) {
+ // get basename with and without suffix
+ const std::string input_basename = boost::filesystem::path(input_file).filename().string();
+ pp.set("input_filename", input_basename);
+ const std::string input_basename_base = input_basename.substr(0, input_basename.find_last_of("."));
+ pp.set("input_filename_base", input_basename_base);
+ }
+ }
+}
+
+bool Print::apply_config(DynamicPrintConfig config)
+{
+ tbb::mutex::scoped_lock lock(m_mutex);
+
+ // we get a copy of the config object so we can modify it safely
+ config.normalize();
+
+ // apply variables to placeholder parser
+ m_placeholder_parser.apply_config(config);
+
+ // handle changes to print config
+ t_config_option_keys print_diff = m_config.diff(config);
+ m_config.apply_only(config, print_diff, true);
+ bool invalidated = this->invalidate_state_by_config_options(print_diff);
+
+ // handle changes to object config defaults
+ m_default_object_config.apply(config, true);
+ for (PrintObject *object : m_objects) {
+ // we don't assume that config contains a full ObjectConfig,
+ // so we base it on the current print-wise default
+ PrintObjectConfig new_config = this->default_object_config();
+ // we override the new config with object-specific options
+ normalize_and_apply_config(new_config, object->model_object()->config);
+ // Force a refresh of a variable layer height profile at the PrintObject if it is not valid.
+ if (! object->layer_height_profile_valid) {
+ // The layer_height_profile is not valid for some reason (updated by the user or invalidated due to some option change).
+ // Invalidate the slicing step, which in turn invalidates everything.
+ object->invalidate_step(posSlice);
+ // Trigger recalculation.
+ invalidated = true;
+ }
+ // check whether the new config is different from the current one
+ t_config_option_keys diff = object->config().diff(new_config);
+ object->config_apply_only(new_config, diff, true);
+ invalidated |= object->invalidate_state_by_config_options(diff);
+ }
+
+ // handle changes to regions config defaults
+ m_default_region_config.apply(config, true);
+
+ // All regions now have distinct settings.
+ // Check whether applying the new region config defaults we'd get different regions.
+ bool rearrange_regions = false;
+ {
+ // Collect the already visited region configs into other_region_configs,
+ // so one may check for duplicates.
+ std::vector<PrintRegionConfig> other_region_configs;
+ for (size_t region_id = 0; region_id < m_regions.size(); ++ region_id) {
+ PrintRegion &region = *m_regions[region_id];
+ PrintRegionConfig this_region_config;
+ bool this_region_config_set = false;
+ for (PrintObject *object : m_objects) {
+ if (region_id < object->region_volumes.size()) {
+ for (int volume_id : object->region_volumes[region_id]) {
+ const ModelVolume &volume = *object->model_object()->volumes[volume_id];
+ if (this_region_config_set) {
+ // If the new config for this volume differs from the other
+ // volume configs currently associated to this region, it means
+ // the region subdivision does not make sense anymore.
+ if (! this_region_config.equals(this->_region_config_from_model_volume(volume))) {
+ rearrange_regions = true;
+ goto exit_for_rearrange_regions;
+ }
+ } else {
+ this_region_config = this->_region_config_from_model_volume(volume);
+ this_region_config_set = true;
+ }
+ for (const PrintRegionConfig &cfg : other_region_configs) {
+ // If the new config for this volume equals any of the other
+ // volume configs that are not currently associated to this
+ // region, it means the region subdivision does not make
+ // sense anymore.
+ if (cfg.equals(this_region_config)) {
+ rearrange_regions = true;
+ goto exit_for_rearrange_regions;
+ }
+ }
+ }
+ }
+ }
+ if (this_region_config_set) {
+ t_config_option_keys diff = region.config().diff(this_region_config);
+ if (! diff.empty()) {
+ region.config_apply_only(this_region_config, diff, false);
+ for (PrintObject *object : m_objects)
+ if (region_id < object->region_volumes.size() && ! object->region_volumes[region_id].empty())
+ invalidated |= object->invalidate_state_by_config_options(diff);
+ }
+ other_region_configs.emplace_back(std::move(this_region_config));
+ }
+ }
+ }
+
+exit_for_rearrange_regions:
+
+ if (rearrange_regions) {
+ // The current subdivision of regions does not make sense anymore.
+ // We need to remove all objects and re-add them.
+ ModelObjectPtrs model_objects;
+ model_objects.reserve(m_objects.size());
+ for (PrintObject *object : m_objects)
+ model_objects.push_back(object->model_object());
+ this->clear_objects();
+ for (ModelObject *mo : model_objects)
+ this->add_model_object(mo);
+ invalidated = true;
+ }
+
+ // Always make sure that the layer_height_profiles are set, as they should not be modified from the worker threads.
+ for (PrintObject *object : m_objects)
+ if (! object->layer_height_profile_valid)
+ object->update_layer_height_profile();
+
+ return invalidated;
+}
+
+bool Print::has_infinite_skirt() const
+{
+ return (m_config.skirt_height == -1 && m_config.skirts > 0)
+ || (m_config.ooze_prevention && this->extruders().size() > 1);
+}
+
+bool Print::has_skirt() const
+{
+ return (m_config.skirt_height > 0 && m_config.skirts > 0)
+ || this->has_infinite_skirt();
+}
+
+std::string Print::validate() const
+{
+ BoundingBox bed_box_2D = get_extents(Polygon::new_scale(m_config.bed_shape.values));
+ BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), scale_(m_config.max_print_height)));
+ // Allow the objects to protrude below the print bed, only the part of the object above the print bed will be sliced.
+ print_volume.min(2) = -1e10;
+ unsigned int printable_count = 0;
+ {
+ // Lock due to the po->reload_model_instances()
+ tbb::mutex::scoped_lock lock(m_mutex);
+ for (PrintObject *po : m_objects) {
+ po->model_object()->check_instances_print_volume_state(print_volume);
+ po->reload_model_instances();
+ if (po->is_printable())
+ ++ printable_count;
+ }
+ }
+
+ if (printable_count == 0)
+ return L("All objects are outside of the print volume.");
+
+ if (m_config.complete_objects) {
+ // Check horizontal clearance.
+ {
+ Polygons convex_hulls_other;
+ for (PrintObject *object : m_objects) {
+ // Get convex hull of all meshes assigned to this print object.
+ Polygon convex_hull;
+ {
+ Polygons mesh_convex_hulls;
+ for (const std::vector<int> &volumes : object->region_volumes)
+ for (int volume_id : volumes)
+ mesh_convex_hulls.emplace_back(object->model_object()->volumes[volume_id]->mesh.convex_hull());
+ // make a single convex hull for all of them
+ convex_hull = Slic3r::Geometry::convex_hull(mesh_convex_hulls);
+ }
+ // Apply the same transformations we apply to the actual meshes when slicing them.
+ object->model_object()->instances.front()->transform_polygon(&convex_hull);
+ // Grow convex hull with the clearance margin.
+ convex_hull = offset(convex_hull, scale_(m_config.extruder_clearance_radius.value)/2, jtRound, scale_(0.1)).front();
+ // Now we check that no instance of convex_hull intersects any of the previously checked object instances.
+ for (const Point &copy : object->m_copies) {
+ Polygon p = convex_hull;
+ p.translate(copy);
+ if (! intersection(convex_hulls_other, p).empty())
+ return L("Some objects are too close; your extruder will collide with them.");
+ polygons_append(convex_hulls_other, p);
+ }
+ }
+ }
+ // Check vertical clearance.
+ {
+ std::vector<coord_t> object_height;
+ for (const PrintObject *object : m_objects)
+ object_height.insert(object_height.end(), object->copies().size(), object->size(2));
+ std::sort(object_height.begin(), object_height.end());
+ // Ignore the tallest *copy* (this is why we repeat height for all of them):
+ // it will be printed as last one so its height doesn't matter.
+ object_height.pop_back();
+ if (! object_height.empty() && object_height.back() > scale_(m_config.extruder_clearance_height.value))
+ return L("Some objects are too tall and cannot be printed without extruder collisions.");
+ }
+ } // end if (m_config.complete_objects)
+
+ if (m_config.spiral_vase) {
+ size_t total_copies_count = 0;
+ for (const PrintObject *object : m_objects)
+ total_copies_count += object->copies().size();
+ // #4043
+ if (total_copies_count > 1 && ! m_config.complete_objects.value)
+ return L("The Spiral Vase option can only be used when printing a single object.");
+ if (m_regions.size() > 1)
+ return L("The Spiral Vase option can only be used when printing single material objects.");
+ }
+
+ if (m_config.single_extruder_multi_material) {
+ for (size_t i=1; i<m_config.nozzle_diameter.values.size(); ++i)
+ if (m_config.nozzle_diameter.values[i] != m_config.nozzle_diameter.values[i-1])
+ return L("All extruders must have the same diameter for single extruder multimaterial printer.");
+ }
+
+ if (this->has_wipe_tower() && ! m_objects.empty()) {
+ if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfMarlin)
+ return L("The Wipe Tower is currently only supported for the Marlin and RepRap/Sprinter G-code flavors.");
+ if (! m_config.use_relative_e_distances)
+ return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).");
+ SlicingParameters slicing_params0 = m_objects.front()->slicing_parameters();
+
+ const PrintObject* tallest_object = m_objects.front(); // let's find the tallest object
+ for (const auto* object : m_objects)
+ if (*(object->layer_height_profile.end()-2) > *(tallest_object->layer_height_profile.end()-2) )
+ tallest_object = object;
+
+ for (PrintObject *object : m_objects) {
+ SlicingParameters slicing_params = object->slicing_parameters();
+ if (std::abs(slicing_params.first_print_layer_height - slicing_params0.first_print_layer_height) > EPSILON ||
+ std::abs(slicing_params.layer_height - slicing_params0.layer_height ) > EPSILON)
+ return L("The Wipe Tower is only supported for multiple objects if they have equal layer heigths");
+ if (slicing_params.raft_layers() != slicing_params0.raft_layers())
+ return L("The Wipe Tower is only supported for multiple objects if they are printed over an equal number of raft layers");
+ if (object->config().support_material_contact_distance != m_objects.front()->config().support_material_contact_distance)
+ return L("The Wipe Tower is only supported for multiple objects if they are printed with the same support_material_contact_distance");
+ if (! equal_layering(slicing_params, slicing_params0))
+ return L("The Wipe Tower is only supported for multiple objects if they are sliced equally.");
+ bool was_layer_height_profile_valid = object->layer_height_profile_valid;
+ object->update_layer_height_profile();
+ object->layer_height_profile_valid = was_layer_height_profile_valid;
+
+ if ( m_config.variable_layer_height ) { // comparing layer height profiles
+ bool failed = false;
+ if (tallest_object->layer_height_profile.size() >= object->layer_height_profile.size() ) {
+ int i = 0;
+ while ( i < object->layer_height_profile.size() && i < tallest_object->layer_height_profile.size()) {
+ if (std::abs(tallest_object->layer_height_profile[i] - object->layer_height_profile[i])) {
+ failed = true;
+ break;
+ }
+ ++i;
+ if (i == object->layer_height_profile.size()-2) // this element contains this objects max z
+ if (tallest_object->layer_height_profile[i] > object->layer_height_profile[i]) // the difference does not matter in this case
+ ++i;
+ }
+ }
+ else
+ failed = true;
+
+ if (failed)
+ return L("The Wipe tower is only supported if all objects have the same layer height profile");
+ }
+ }
+ }
+
+ {
+ // find the smallest nozzle diameter
+ std::vector<unsigned int> extruders = this->extruders();
+ if (extruders.empty())
+ return L("The supplied settings will cause an empty print.");
+
+ std::vector<double> nozzle_diameters;
+ for (unsigned int extruder_id : extruders)
+ nozzle_diameters.push_back(m_config.nozzle_diameter.get_at(extruder_id));
+ double min_nozzle_diameter = *std::min_element(nozzle_diameters.begin(), nozzle_diameters.end());
+ unsigned int total_extruders_count = m_config.nozzle_diameter.size();
+ for (const auto& extruder_idx : extruders)
+ if ( extruder_idx >= total_extruders_count )
+ return L("One or more object were assigned an extruder that the printer does not have.");
+
+ for (PrintObject *object : m_objects) {
+ if ((object->config().support_material_extruder == -1 || object->config().support_material_interface_extruder == -1) &&
+ (object->config().raft_layers > 0 || object->config().support_material.value)) {
+ // The object has some form of support and either support_material_extruder or support_material_interface_extruder
+ // will be printed with the current tool without a forced tool change. Play safe, assert that all object nozzles
+ // are of the same diameter.
+ if (nozzle_diameters.size() > 1)
+ return L("Printing with multiple extruders of differing nozzle diameters. "
+ "If support is to be printed with the current extruder (support_material_extruder == 0 or support_material_interface_extruder == 0), "
+ "all nozzles have to be of the same diameter.");
+ }
+
+ // validate first_layer_height
+ double first_layer_height = object->config().get_abs_value(L("first_layer_height"));
+ double first_layer_min_nozzle_diameter;
+ if (object->config().raft_layers > 0) {
+ // if we have raft layers, only support material extruder is used on first layer
+ size_t first_layer_extruder = object->config().raft_layers == 1
+ ? object->config().support_material_interface_extruder-1
+ : object->config().support_material_extruder-1;
+ first_layer_min_nozzle_diameter = (first_layer_extruder == size_t(-1)) ?
+ min_nozzle_diameter :
+ m_config.nozzle_diameter.get_at(first_layer_extruder);
+ } else {
+ // if we don't have raft layers, any nozzle diameter is potentially used in first layer
+ first_layer_min_nozzle_diameter = min_nozzle_diameter;
+ }
+ if (first_layer_height > first_layer_min_nozzle_diameter)
+ return L("First layer height can't be greater than nozzle diameter");
+
+ // validate layer_height
+ if (object->config().layer_height.value > min_nozzle_diameter)
+ return L("Layer height can't be greater than nozzle diameter");
+ }
+ }
+
+ return std::string();
+}
+
+// the bounding box of objects placed in copies position
+// (without taking skirt/brim/support material into account)
+BoundingBox Print::bounding_box() const
+{
+ BoundingBox bb;
+ for (const PrintObject *object : m_objects)
+ for (Point copy : object->m_copies) {
+ bb.merge(copy);
+ copy += to_2d(object->size);
+ bb.merge(copy);
+ }
+ return bb;
+}
+
+// the total bounding box of extrusions, including skirt/brim/support material
+// this methods needs to be called even when no steps were processed, so it should
+// only use configuration values
+BoundingBox Print::total_bounding_box() const
+{
+ // get objects bounding box
+ BoundingBox bb = this->bounding_box();
+
+ // we need to offset the objects bounding box by at least half the perimeters extrusion width
+ Flow perimeter_flow = m_objects.front()->get_layer(0)->get_region(0)->flow(frPerimeter);
+ double extra = perimeter_flow.width/2;
+
+ // consider support material
+ if (this->has_support_material()) {
+ extra = std::max(extra, SUPPORT_MATERIAL_MARGIN);
+ }
+
+ // consider brim and skirt
+ if (m_config.brim_width.value > 0) {
+ Flow brim_flow = this->brim_flow();
+ extra = std::max(extra, m_config.brim_width.value + brim_flow.width/2);
+ }
+ if (this->has_skirt()) {
+ int skirts = m_config.skirts.value;
+ if (skirts == 0 && this->has_infinite_skirt()) skirts = 1;
+ Flow skirt_flow = this->skirt_flow();
+ extra = std::max(
+ extra,
+ m_config.brim_width.value
+ + m_config.skirt_distance.value
+ + skirts * skirt_flow.spacing()
+ + skirt_flow.width/2
+ );
+ }
+
+ if (extra > 0)
+ bb.offset(scale_(extra));
+
+ return bb;
+}
+
+double Print::skirt_first_layer_height() const
+{
+ if (m_objects.empty())
+ throw std::invalid_argument("skirt_first_layer_height() can't be called without PrintObjects");
+ return m_objects.front()->config().get_abs_value("first_layer_height");
+}
+
+Flow Print::brim_flow() const
+{
+ ConfigOptionFloatOrPercent width = m_config.first_layer_extrusion_width;
+ if (width.value == 0)
+ width = m_regions.front()->config().perimeter_extrusion_width;
+ if (width.value == 0)
+ width = m_objects.front()->config().extrusion_width;
+
+ /* We currently use a random region's perimeter extruder.
+ While this works for most cases, we should probably consider all of the perimeter
+ extruders and take the one with, say, the smallest index.
+ The same logic should be applied to the code that selects the extruder during G-code
+ generation as well. */
+ return Flow::new_from_config_width(
+ frPerimeter,
+ width,
+ m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1),
+ this->skirt_first_layer_height(),
+ 0
+ );
+}
+
+Flow Print::skirt_flow() const
+{
+ ConfigOptionFloatOrPercent width = m_config.first_layer_extrusion_width;
+ if (width.value == 0)
+ width = m_regions.front()->config().perimeter_extrusion_width;
+ if (width.value == 0)
+ width = m_objects.front()->config().extrusion_width;
+
+ /* We currently use a random object's support material extruder.
+ While this works for most cases, we should probably consider all of the support material
+ extruders and take the one with, say, the smallest index;
+ The same logic should be applied to the code that selects the extruder during G-code
+ generation as well. */
+ return Flow::new_from_config_width(
+ frPerimeter,
+ width,
+ m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1),
+ this->skirt_first_layer_height(),
+ 0
+ );
+}
+
+PrintRegionConfig Print::_region_config_from_model_volume(const ModelVolume &volume)
+{
+ PrintRegionConfig config = this->default_region_config();
+ normalize_and_apply_config(config, volume.get_object()->config);
+ normalize_and_apply_config(config, volume.config);
+ if (! volume.material_id().empty())
+ normalize_and_apply_config(config, volume.material()->config);
+ return config;
+}
+
+bool Print::has_support_material() const
+{
+ for (const PrintObject *object : m_objects)
+ if (object->has_support_material())
+ return true;
+ return false;
+}
+
+/* This method assigns extruders to the volumes having a material
+ but not having extruders set in the volume config. */
+void Print::auto_assign_extruders(ModelObject* model_object) const
+{
+ // only assign extruders if object has more than one volume
+ if (model_object->volumes.size() < 2)
+ return;
+
+// size_t extruders = m_config.nozzle_diameter.values.size();
+ for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) {
+ ModelVolume *volume = model_object->volumes[volume_id];
+ //FIXME Vojtech: This assigns an extruder ID even to a modifier volume, if it has a material assigned.
+ if ((volume->is_model_part() || volume->is_modifier()) && ! volume->material_id().empty() && ! volume->config.has("extruder"))
+ volume->config.opt<ConfigOptionInt>("extruder", true)->value = int(volume_id + 1);
+ }
+}
+
+// Slicing process, running at a background thread.
+void Print::process()
+{
+ BOOST_LOG_TRIVIAL(info) << "Staring the slicing process.";
+ for (PrintObject *obj : m_objects)
+ obj->make_perimeters();
+ this->throw_if_canceled();
+ this->set_status(70, "Infilling layers");
+ for (PrintObject *obj : m_objects)
+ obj->infill();
+ this->throw_if_canceled();
+ for (PrintObject *obj : m_objects)
+ obj->generate_support_material();
+ this->throw_if_canceled();
+ if (! m_state.is_done(psSkirt)) {
+ this->set_started(psSkirt);
+ m_skirt.clear();
+ if (this->has_skirt()) {
+ this->set_status(88, "Generating skirt");
+ this->_make_skirt();
+ }
+ this->set_done(psSkirt);
+ }
+ this->throw_if_canceled();
+ if (! m_state.is_done(psBrim)) {
+ this->set_started(psBrim);
+ m_brim.clear();
+ if (m_config.brim_width > 0) {
+ this->set_status(88, "Generating brim");
+ this->_make_brim();
+ }
+ this->set_done(psBrim);
+ }
+ this->throw_if_canceled();
+ if (! m_state.is_done(psWipeTower)) {
+ this->set_started(psWipeTower);
+ m_wipe_tower_data.clear();
+ if (this->has_wipe_tower()) {
+ //this->set_status(95, "Generating wipe tower");
+ this->_make_wipe_tower();
+ }
+ this->set_done(psWipeTower);
+ }
+ BOOST_LOG_TRIVIAL(info) << "Slicing process finished.";
+}
+
+// G-code export process, running at a background thread.
+// The export_gcode may die for various reasons (fails to process output_filename_format,
+// write error into the G-code, cannot execute post-processing scripts).
+// It is up to the caller to show an error message.
+void Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data)
+{
+ // prerequisites
+ this->process();
+
+ // output everything to a G-code file
+ // The following call may die if the output_filename_format template substitution fails.
+ std::string path = this->output_filepath(path_template);
+ std::string message = "Exporting G-code";
+ if (! path.empty()) {
+ message += " to ";
+ message += path;
+ }
+ this->set_status(90, message);
+
+ // The following line may die for multiple reasons.
+ GCode gcode;
+ gcode.do_export(this, path.c_str(), preview_data);
+}
+
+void Print::_make_skirt()
+{
+ // First off we need to decide how tall the skirt must be.
+ // The skirt_height option from config is expressed in layers, but our
+ // object might have different layer heights, so we need to find the print_z
+ // of the highest layer involved.
+ // Note that unless has_infinite_skirt() == true
+ // the actual skirt might not reach this $skirt_height_z value since the print
+ // order of objects on each layer is not guaranteed and will not generally
+ // include the thickest object first. It is just guaranteed that a skirt is
+ // prepended to the first 'n' layers (with 'n' = skirt_height).
+ // $skirt_height_z in this case is the highest possible skirt height for safety.
+ coordf_t skirt_height_z = 0.;
+ PrintObjectPtrs printable_objects = get_printable_objects();
+ for (const PrintObject *object : printable_objects) {
+ size_t skirt_layers = this->has_infinite_skirt() ?
+ object->layer_count() :
+ std::min(size_t(m_config.skirt_height.value), object->layer_count());
+ skirt_height_z = std::max(skirt_height_z, object->m_layers[skirt_layers-1]->print_z);
+ }
+
+ // Collect points from all layers contained in skirt height.
+ Points points;
+ for (const PrintObject *object : printable_objects) {
+ Points object_points;
+ // Get object layers up to skirt_height_z.
+ for (const Layer *layer : object->m_layers) {
+ if (layer->print_z > skirt_height_z)
+ break;
+ for (const ExPolygon &expoly : layer->slices.expolygons)
+ // Collect the outer contour points only, ignore holes for the calculation of the convex hull.
+ append(object_points, expoly.contour.points);
+ }
+ // Get support layers up to skirt_height_z.
+ for (const SupportLayer *layer : object->support_layers()) {
+ if (layer->print_z > skirt_height_z)
+ break;
+ for (const ExtrusionEntity *extrusion_entity : layer->support_fills.entities)
+ append(object_points, extrusion_entity->as_polyline().points);
+ }
+ // Repeat points for each object copy.
+ for (const Point &shift : object->m_copies) {
+ Points copy_points = object_points;
+ for (Point &pt : copy_points)
+ pt += shift;
+ append(points, copy_points);
+ }
+ }
+
+ if (points.size() < 3)
+ // At least three points required for a convex hull.
+ return;
+
+ this->throw_if_canceled();
+ Polygon convex_hull = Slic3r::Geometry::convex_hull(points);
+
+ // Skirt may be printed on several layers, having distinct layer heights,
+ // but loops must be aligned so can't vary width/spacing
+ // TODO: use each extruder's own flow
+ double first_layer_height = this->skirt_first_layer_height();
+ Flow flow = this->skirt_flow();
+ float spacing = flow.spacing();
+ double mm3_per_mm = flow.mm3_per_mm();
+
+ std::vector<size_t> extruders;
+ std::vector<double> extruders_e_per_mm;
+ {
+ auto set_extruders = this->extruders();
+ extruders.reserve(set_extruders.size());
+ extruders_e_per_mm.reserve(set_extruders.size());
+ for (auto &extruder_id : set_extruders) {
+ extruders.push_back(extruder_id);
+ extruders_e_per_mm.push_back(Extruder((unsigned int)extruder_id, &m_config).e_per_mm(mm3_per_mm));
+ }
+ }
+
+ // Number of skirt loops per skirt layer.
+ int n_skirts = m_config.skirts.value;
+ if (this->has_infinite_skirt() && n_skirts == 0)
+ n_skirts = 1;
+
+ // Initial offset of the brim inner edge from the object (possible with a support & raft).
+ // The skirt will touch the brim if the brim is extruded.
+ Flow brim_flow = this->brim_flow();
+ double actual_brim_width = brim_flow.spacing() * floor(m_config.brim_width.value / brim_flow.spacing());
+ coord_t distance = scale_(std::max(m_config.skirt_distance.value, actual_brim_width) - spacing/2.);
+ // Draw outlines from outside to inside.
+ // Loop while we have less skirts than required or any extruder hasn't reached the min length if any.
+ std::vector<coordf_t> extruded_length(extruders.size(), 0.);
+ for (int i = n_skirts, extruder_idx = 0; i > 0; -- i) {
+ this->throw_if_canceled();
+ // Offset the skirt outside.
+ distance += coord_t(scale_(spacing));
+ // Generate the skirt centerline.
+ Polygon loop;
+ {
+ Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, scale_(0.1));
+ Geometry::simplify_polygons(loops, scale_(0.05), &loops);
+ loop = loops.front();
+ }
+ // Extrude the skirt loop.
+ ExtrusionLoop eloop(elrSkirt);
+ eloop.paths.emplace_back(ExtrusionPath(
+ ExtrusionPath(
+ erSkirt,
+ mm3_per_mm, // this will be overridden at G-code export time
+ flow.width,
+ first_layer_height // this will be overridden at G-code export time
+ )));
+ eloop.paths.back().polyline = loop.split_at_first_point();
+ m_skirt.append(eloop);
+ if (m_config.min_skirt_length.value > 0) {
+ // The skirt length is limited. Sum the total amount of filament length extruded, in mm.
+ extruded_length[extruder_idx] += unscale<double>(loop.length()) * extruders_e_per_mm[extruder_idx];
+ if (extruded_length[extruder_idx] < m_config.min_skirt_length.value) {
+ // Not extruded enough yet with the current extruder. Add another loop.
+ if (i == 1)
+ ++ i;
+ } else {
+ assert(extruded_length[extruder_idx] >= m_config.min_skirt_length.value);
+ // Enough extruded with the current extruder. Extrude with the next one,
+ // until the prescribed number of skirt loops is extruded.
+ if (extruder_idx + 1 < extruders.size())
+ ++ extruder_idx;
+ }
+ } else {
+ // The skirt lenght is not limited, extrude the skirt with the 1st extruder only.
+ }
+ }
+ // Brims were generated inside out, reverse to print the outmost contour first.
+ m_skirt.reverse();
+}
+
+void Print::_make_brim()
+{
+ // Brim is only printed on first layer and uses perimeter extruder.
+ Flow flow = this->brim_flow();
+ Polygons islands;
+ PrintObjectPtrs printable_objects = get_printable_objects();
+ for (PrintObject *object : printable_objects) {
+ Polygons object_islands;
+ for (ExPolygon &expoly : object->m_layers.front()->slices.expolygons)
+ object_islands.push_back(expoly.contour);
+ if (! object->support_layers().empty())
+ object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON));
+ islands.reserve(islands.size() + object_islands.size() * object->m_copies.size());
+ for (const Point &pt : object->m_copies)
+ for (Polygon &poly : object_islands) {
+ islands.push_back(poly);
+ islands.back().translate(pt);
+ }
+ }
+ Polygons loops;
+ size_t num_loops = size_t(floor(m_config.brim_width.value / flow.spacing()));
+ for (size_t i = 0; i < num_loops; ++ i) {
+ this->throw_if_canceled();
+ islands = offset(islands, float(flow.scaled_spacing()), jtSquare);
+ for (Polygon &poly : islands) {
+ // poly.simplify(SCALED_RESOLUTION);
+ poly.points.push_back(poly.points.front());
+ Points p = MultiPoint::_douglas_peucker(poly.points, SCALED_RESOLUTION);
+ p.pop_back();
+ poly.points = std::move(p);
+ }
+ polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing())));
+ }
+
+ loops = union_pt_chained(loops, false);
+ std::reverse(loops.begin(), loops.end());
+ extrusion_entities_append_loops(m_brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()));
+}
+
+// Wipe tower support.
+bool Print::has_wipe_tower() const
+{
+ return
+ m_config.single_extruder_multi_material.value &&
+ ! m_config.spiral_vase.value &&
+ m_config.wipe_tower.value &&
+ m_config.nozzle_diameter.values.size() > 1;
+}
+
+void Print::_make_wipe_tower()
+{
+ m_wipe_tower_data.clear();
+ if (! this->has_wipe_tower())
+ return;
+
+ // Get wiping matrix to get number of extruders and convert vector<double> to vector<float>:
+ std::vector<float> wiping_matrix(cast<float>(m_config.wiping_volumes_matrix.values));
+ // Extract purging volumes for each extruder pair:
+ std::vector<std::vector<float>> wipe_volumes;
+ const unsigned int number_of_extruders = (unsigned int)(sqrt(wiping_matrix.size())+EPSILON);
+ for (unsigned int i = 0; i<number_of_extruders; ++i)
+ wipe_volumes.push_back(std::vector<float>(wiping_matrix.begin()+i*number_of_extruders, wiping_matrix.begin()+(i+1)*number_of_extruders));
+
+ // Let the ToolOrdering class know there will be initial priming extrusions at the start of the print.
+ m_wipe_tower_data.tool_ordering = ToolOrdering(*this, (unsigned int)-1, true);
+ if (! m_wipe_tower_data.tool_ordering.has_wipe_tower())
+ // Don't generate any wipe tower.
+ return;
+
+ // Check whether there are any layers in m_tool_ordering, which are marked with has_wipe_tower,
+ // they print neither object, nor support. These layers are above the raft and below the object, and they
+ // shall be added to the support layers to be printed.
+ // see https://github.com/prusa3d/Slic3r/issues/607
+ {
+ size_t idx_begin = size_t(-1);
+ size_t idx_end = m_wipe_tower_data.tool_ordering.layer_tools().size();
+ // Find the first wipe tower layer, which does not have a counterpart in an object or a support layer.
+ for (size_t i = 0; i < idx_end; ++ i) {
+ const LayerTools &lt = m_wipe_tower_data.tool_ordering.layer_tools()[i];
+ if (lt.has_wipe_tower && ! lt.has_object && ! lt.has_support) {
+ idx_begin = i;
+ break;
+ }
+ }
+ if (idx_begin != size_t(-1)) {
+ // Find the position in m_objects.first()->support_layers to insert these new support layers.
+ double wipe_tower_new_layer_print_z_first = m_wipe_tower_data.tool_ordering.layer_tools()[idx_begin].print_z;
+ SupportLayerPtrs::const_iterator it_layer = m_objects.front()->support_layers().begin();
+ SupportLayerPtrs::const_iterator it_end = m_objects.front()->support_layers().end();
+ for (; it_layer != it_end && (*it_layer)->print_z - EPSILON < wipe_tower_new_layer_print_z_first; ++ it_layer);
+ // Find the stopper of the sequence of wipe tower layers, which do not have a counterpart in an object or a support layer.
+ for (size_t i = idx_begin; i < idx_end; ++ i) {
+ LayerTools &lt = const_cast<LayerTools&>(m_wipe_tower_data.tool_ordering.layer_tools()[i]);
+ if (! (lt.has_wipe_tower && ! lt.has_object && ! lt.has_support))
+ break;
+ lt.has_support = true;
+ // Insert the new support layer.
+ double height = lt.print_z - m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z;
+ //FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway.
+ it_layer = m_objects.front()->insert_support_layer(it_layer, size_t(-1), height, lt.print_z, lt.print_z - 0.5 * height);
+ ++ it_layer;
+ }
+ }
+ }
+ this->throw_if_canceled();
+
+ // Initialize the wipe tower.
+ WipeTowerPrusaMM wipe_tower(
+ float(m_config.wipe_tower_x.value), float(m_config.wipe_tower_y.value),
+ float(m_config.wipe_tower_width.value),
+ float(m_config.wipe_tower_rotation_angle.value), float(m_config.cooling_tube_retraction.value),
+ float(m_config.cooling_tube_length.value), float(m_config.parking_pos_retraction.value),
+ float(m_config.extra_loading_move.value), float(m_config.wipe_tower_bridging), wipe_volumes,
+ m_wipe_tower_data.tool_ordering.first_extruder());
+
+ //wipe_tower.set_retract();
+ //wipe_tower.set_zhop();
+
+ // Set the extruder & material properties at the wipe tower object.
+ for (size_t i = 0; i < number_of_extruders; ++ i)
+ wipe_tower.set_extruder(
+ i,
+ WipeTowerPrusaMM::parse_material(m_config.filament_type.get_at(i).c_str()),
+ m_config.temperature.get_at(i),
+ m_config.first_layer_temperature.get_at(i),
+ m_config.filament_loading_speed.get_at(i),
+ m_config.filament_loading_speed_start.get_at(i),
+ m_config.filament_unloading_speed.get_at(i),
+ m_config.filament_unloading_speed_start.get_at(i),
+ m_config.filament_toolchange_delay.get_at(i),
+ m_config.filament_cooling_moves.get_at(i),
+ m_config.filament_cooling_initial_speed.get_at(i),
+ m_config.filament_cooling_final_speed.get_at(i),
+ m_config.filament_ramming_parameters.get_at(i),
+ m_config.nozzle_diameter.get_at(i));
+
+ m_wipe_tower_data.priming = Slic3r::make_unique<WipeTower::ToolChangeResult>(
+ wipe_tower.prime(this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false));
+
+ // Lets go through the wipe tower layers and determine pairs of extruder changes for each
+ // to pass to wipe_tower (so that it can use it for planning the layout of the tower)
+ {
+ unsigned int current_extruder_id = m_wipe_tower_data.tool_ordering.all_extruders().back();
+ for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers
+ if (!layer_tools.has_wipe_tower) continue;
+ bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front();
+ wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id,false);
+ for (const auto extruder_id : layer_tools.extruders) {
+ if ((first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back()) || extruder_id != current_extruder_id) {
+ float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id]; // total volume to wipe after this toolchange
+ // Not all of that can be used for infill purging:
+ volume_to_wipe -= m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
+
+ // try to assign some infills/objects for the wiping:
+ volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_wipe);
+
+ // add back the minimal amount toforce on the wipe tower:
+ volume_to_wipe += m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
+
+ // request a toolchange at the wipe tower with at least volume_to_wipe purging amount
+ wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id,
+ first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back(), volume_to_wipe);
+ current_extruder_id = extruder_id;
+ }
+ }
+ layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
+ if (&layer_tools == &m_wipe_tower_data.tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0)
+ break;
+ }
+ }
+
+ // Generate the wipe tower layers.
+ m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size());
+ wipe_tower.generate(m_wipe_tower_data.tool_changes);
+ m_wipe_tower_data.depth = wipe_tower.get_depth();
+
+ // Unload the current filament over the purge tower.
+ coordf_t layer_height = m_objects.front()->config().layer_height.value;
+ if (m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions > 0) {
+ // The wipe tower goes up to the last layer of the print.
+ if (wipe_tower.layer_finished()) {
+ // The wipe tower is printed to the top of the print and it has no space left for the final extruder purge.
+ // Lift Z to the next layer.
+ wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z + layer_height), float(layer_height), 0, false, true);
+ } else {
+ // There is yet enough space at this layer of the wipe tower for the final purge.
+ }
+ } else {
+ // The wipe tower does not reach the last print layer, perform the pruge at the last print layer.
+ assert(m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions == 0);
+ wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z), float(layer_height), 0, false, true);
+ }
+ m_wipe_tower_data.final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>(
+ wipe_tower.tool_change((unsigned int)-1, false));
+
+ m_wipe_tower_data.used_filament = wipe_tower.get_used_filament();
+ m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges();
+}
+
+std::string Print::output_filename() const
+{
+ DynamicConfig cfg_timestamp;
+ PlaceholderParser::update_timestamp(cfg_timestamp);
+ try {
+ return this->placeholder_parser().process(m_config.output_filename_format.value, 0, &cfg_timestamp);
+ } catch (std::runtime_error &err) {
+ throw std::runtime_error(L("Failed processing of the output_filename_format template.") + "\n" + err.what());
+ }
+}
+
+std::string Print::output_filepath(const std::string &path) const
+{
+ // if we were supplied no path, generate an automatic one based on our first object's input file
+ if (path.empty()) {
+ // get the first input file name
+ std::string input_file;
+ for (const PrintObject *object : m_objects) {
+ input_file = object->model_object()->input_file;
+ if (! input_file.empty())
+ break;
+ }
+ return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).make_preferred().string();
+ }
+
+ // if we were supplied a directory, use it and append our automatically generated filename
+ boost::filesystem::path p(path);
+ if (boost::filesystem::is_directory(p))
+ return (p / this->output_filename()).make_preferred().string();
+
+ // if we were supplied a file which is not a directory, use it
+ return path;
+}
+
+void Print::export_png(const std::string &dirpath)
+{
+// size_t idx = 0;
+// for (PrintObject *obj : m_objects) {
+// obj->slice();
+// this->set_status(int(floor(idx * 100. / m_objects.size() + 0.5)), "Slicing...");
+// ++ idx;
+// }
+// this->set_status(90, "Exporting zipped archive...");
+// print_to<FilePrinterFormat::PNG>(*this,
+// dirpath,
+// float(m_config.bed_size_x.value),
+// float(m_config.bed_size_y.value),
+// int(m_config.pixel_width.value),
+// int(m_config.pixel_height.value),
+// float(m_config.exp_time.value),
+// float(m_config.exp_time_first.value));
+// this->set_status(100, "Done.");
+}
+
+// Returns extruder this eec should be printed with, according to PrintRegion config
+int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion &region)
+{
+ return is_infill(fill.role()) ? std::max<int>(0, (is_solid_infill(fill.entities.front()->role()) ? region.config().solid_infill_extruder : region.config().infill_extruder) - 1) :
+ std::max<int>(region.config().perimeter_extruder.value - 1, 0);
+}
+
+} // namespace Slic3r
+