2013-12-20 00:36:42 +00:00
|
|
|
|
#include "Print.hpp"
|
2014-05-06 08:07:18 +00:00
|
|
|
|
#include "BoundingBox.hpp"
|
2014-11-09 14:27:34 +00:00
|
|
|
|
#include "ClipperUtils.hpp"
|
2017-02-15 10:05:52 +00:00
|
|
|
|
#include "Extruder.hpp"
|
2014-12-12 18:14:52 +00:00
|
|
|
|
#include "Flow.hpp"
|
2014-11-11 20:17:02 +00:00
|
|
|
|
#include "Geometry.hpp"
|
2014-12-12 18:14:52 +00:00
|
|
|
|
#include "SupportMaterial.hpp"
|
2017-05-25 20:27:53 +00:00
|
|
|
|
#include "GCode/WipeTowerPrusaMM.hpp"
|
2014-08-03 16:41:09 +00:00
|
|
|
|
#include <algorithm>
|
2017-05-31 15:02:23 +00:00
|
|
|
|
#include <unordered_set>
|
2016-12-20 18:26:23 +00:00
|
|
|
|
#include <boost/filesystem.hpp>
|
|
|
|
|
#include <boost/lexical_cast.hpp>
|
2013-12-20 00:36:42 +00:00
|
|
|
|
|
|
|
|
|
namespace Slic3r {
|
|
|
|
|
|
2017-05-30 15:17:26 +00:00
|
|
|
|
template class PrintState<PrintStep, psCount>;
|
|
|
|
|
template class PrintState<PrintObjectStep, posCount>;
|
2014-05-06 08:07:18 +00:00
|
|
|
|
|
2017-05-30 18:09:34 +00:00
|
|
|
|
void Print::clear_objects()
|
2014-05-06 08:07:18 +00:00
|
|
|
|
{
|
2017-05-03 16:28:22 +00:00
|
|
|
|
for (int i = int(this->objects.size())-1; i >= 0; --i)
|
2014-05-06 08:07:18 +00:00
|
|
|
|
this->delete_object(i);
|
2017-05-31 15:02:23 +00:00
|
|
|
|
for (PrintRegion *region : this->regions)
|
|
|
|
|
delete region;
|
|
|
|
|
this->regions.clear();
|
2014-05-06 08:07:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-30 18:09:34 +00:00
|
|
|
|
void Print::delete_object(size_t idx)
|
2014-05-06 08:07:18 +00:00
|
|
|
|
{
|
2014-12-12 19:16:32 +00:00
|
|
|
|
// destroy object and remove it from our container
|
2017-05-31 10:55:59 +00:00
|
|
|
|
delete this->objects[idx];
|
2017-05-30 18:09:34 +00:00
|
|
|
|
this->objects.erase(this->objects.begin() + idx);
|
2017-05-31 10:55:59 +00:00
|
|
|
|
this->invalidate_all_steps();
|
2014-05-06 08:07:18 +00:00
|
|
|
|
// TODO: purge unused regions
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-31 15:02:23 +00:00
|
|
|
|
void Print::reload_object(size_t /* idx */)
|
2014-11-07 19:25:05 +00:00
|
|
|
|
{
|
|
|
|
|
/* 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
|
|
|
|
|
ModelObjectPtrs model_objects;
|
2017-05-30 18:09:34 +00:00
|
|
|
|
model_objects.reserve(this->objects.size());
|
|
|
|
|
for (PrintObject *object : this->objects)
|
|
|
|
|
model_objects.push_back(object->model_object());
|
2014-11-07 19:25:05 +00:00
|
|
|
|
// remove our print objects
|
2014-11-12 21:51:48 +00:00
|
|
|
|
this->clear_objects();
|
2014-11-07 19:25:05 +00:00
|
|
|
|
// re-add model objects
|
2017-05-30 18:09:34 +00:00
|
|
|
|
for (ModelObject *mo : model_objects)
|
|
|
|
|
this->add_model_object(mo);
|
2014-11-07 19:25:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-09-01 15:30:18 +00:00
|
|
|
|
// 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.
|
2017-05-30 18:09:34 +00:00
|
|
|
|
bool Print::reload_model_instances()
|
2014-12-29 21:29:24 +00:00
|
|
|
|
{
|
|
|
|
|
bool invalidated = false;
|
2017-05-30 18:09:34 +00:00
|
|
|
|
for (PrintObject *object : this->objects)
|
|
|
|
|
invalidated |= object->reload_model_instances();
|
2014-12-29 21:29:24 +00:00
|
|
|
|
return invalidated;
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-30 18:09:34 +00:00
|
|
|
|
PrintRegion* Print::add_region()
|
2014-05-06 08:07:18 +00:00
|
|
|
|
{
|
2017-05-30 18:09:34 +00:00
|
|
|
|
regions.push_back(new PrintRegion(this));
|
|
|
|
|
return regions.back();
|
2014-05-06 08:07:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-30 18:09:34 +00:00
|
|
|
|
// 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)
|
2014-05-06 08:07:18 +00:00
|
|
|
|
{
|
2017-05-30 18:09:34 +00:00
|
|
|
|
if (opt_keys.empty())
|
|
|
|
|
return false;
|
2014-05-06 08:07:18 +00:00
|
|
|
|
|
2017-05-31 15:02:23 +00:00
|
|
|
|
// Cache the plenty of parameters, which influence the G-code generator only,
|
|
|
|
|
// or they are only notes not influencing the generated G-code.
|
2017-11-28 14:19:57 +00:00
|
|
|
|
static std::unordered_set<std::string> steps_ignore = {
|
|
|
|
|
"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",
|
|
|
|
|
"infill_first",
|
|
|
|
|
"layer_gcode",
|
|
|
|
|
"min_fan_speed",
|
|
|
|
|
"max_fan_speed",
|
2018-03-09 09:40:42 +00:00
|
|
|
|
"max_print_height",
|
2017-11-28 14:19:57 +00:00
|
|
|
|
"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",
|
|
|
|
|
"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"
|
|
|
|
|
};
|
2017-05-31 15:02:23 +00:00
|
|
|
|
|
2017-05-30 15:17:26 +00:00
|
|
|
|
std::vector<PrintStep> steps;
|
|
|
|
|
std::vector<PrintObjectStep> osteps;
|
2017-05-31 15:02:23 +00:00
|
|
|
|
bool invalidated = false;
|
2017-05-30 15:17:26 +00:00
|
|
|
|
for (const t_config_option_key &opt_key : opt_keys) {
|
2017-05-31 15:02:23 +00:00
|
|
|
|
if (steps_ignore.find(opt_key) != steps_ignore.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.
|
|
|
|
|
} else if (
|
|
|
|
|
opt_key == "skirts"
|
2017-05-30 15:17:26 +00:00
|
|
|
|
|| 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);
|
2017-05-31 15:02:23 +00:00
|
|
|
|
} else if (
|
|
|
|
|
opt_key == "nozzle_diameter"
|
2017-05-30 15:17:26 +00:00
|
|
|
|
|| opt_key == "resolution") {
|
|
|
|
|
osteps.emplace_back(posSlice);
|
2017-05-25 20:27:53 +00:00
|
|
|
|
} else if (
|
2017-05-30 15:17:26 +00:00
|
|
|
|
opt_key == "complete_objects"
|
|
|
|
|
|| opt_key == "filament_type"
|
2017-05-31 15:02:23 +00:00
|
|
|
|
|| opt_key == "filament_soluble"
|
2017-05-30 15:17:26 +00:00
|
|
|
|
|| opt_key == "first_layer_temperature"
|
2018-03-05 09:45:35 +00:00
|
|
|
|
|| opt_key == "filament_loading_speed"
|
|
|
|
|
|| opt_key == "filament_unloading_speed"
|
|
|
|
|
|| opt_key == "filament_toolchange_delay"
|
2018-04-24 11:02:08 +00:00
|
|
|
|
|| opt_key == "filament_cooling_moves"
|
|
|
|
|
|| opt_key == "filament_cooling_initial_speed"
|
|
|
|
|
|| opt_key == "filament_cooling_final_speed"
|
2018-03-15 13:04:12 +00:00
|
|
|
|
|| opt_key == "filament_ramming_parameters"
|
2017-05-30 15:17:26 +00:00
|
|
|
|
|| opt_key == "gcode_flavor"
|
|
|
|
|
|| opt_key == "single_extruder_multi_material"
|
|
|
|
|
|| opt_key == "spiral_vase"
|
|
|
|
|
|| opt_key == "temperature"
|
|
|
|
|
|| opt_key == "wipe_tower"
|
|
|
|
|
|| opt_key == "wipe_tower_x"
|
|
|
|
|
|| opt_key == "wipe_tower_y"
|
|
|
|
|
|| opt_key == "wipe_tower_width"
|
2017-11-30 11:08:22 +00:00
|
|
|
|
|| opt_key == "wipe_tower_rotation_angle"
|
2018-03-12 14:41:25 +00:00
|
|
|
|
|| opt_key == "wipe_tower_bridging"
|
2018-03-20 14:07:18 +00:00
|
|
|
|
|| opt_key == "wiping_volumes_matrix"
|
2018-04-12 13:38:05 +00:00
|
|
|
|
|| opt_key == "parking_pos_retraction"
|
|
|
|
|
|| opt_key == "cooling_tube_retraction"
|
|
|
|
|
|| opt_key == "cooling_tube_length"
|
2018-04-16 12:26:57 +00:00
|
|
|
|
|| opt_key == "extra_loading_move"
|
2017-05-30 15:17:26 +00:00
|
|
|
|
|| opt_key == "z_offset") {
|
|
|
|
|
steps.emplace_back(psWipeTower);
|
2017-05-31 15:02:23 +00:00
|
|
|
|
} else if (
|
|
|
|
|
opt_key == "first_layer_extrusion_width"
|
|
|
|
|
|| opt_key == "min_layer_height"
|
|
|
|
|
|| opt_key == "max_layer_height") {
|
2017-05-30 15:17:26 +00:00
|
|
|
|
osteps.emplace_back(posPerimeters);
|
|
|
|
|
osteps.emplace_back(posInfill);
|
|
|
|
|
osteps.emplace_back(posSupportMaterial);
|
|
|
|
|
steps.emplace_back(psSkirt);
|
|
|
|
|
steps.emplace_back(psBrim);
|
|
|
|
|
steps.emplace_back(psWipeTower);
|
2014-06-10 22:15:02 +00:00
|
|
|
|
} else {
|
2014-06-13 09:19:53 +00:00
|
|
|
|
// for legacy, if we can't handle this option let's invalidate all steps
|
2017-05-31 10:55:59 +00:00
|
|
|
|
//FIXME invalidate all steps of all objects as well?
|
2017-05-31 15:02:23 +00:00
|
|
|
|
invalidated |= this->invalidate_all_steps();
|
|
|
|
|
// Continue with the other opt_keys to possibly invalidate any object specific steps.
|
2014-06-10 22:15:02 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-31 15:02:23 +00:00
|
|
|
|
|
2017-05-30 15:17:26 +00:00
|
|
|
|
sort_remove_duplicates(steps);
|
|
|
|
|
for (PrintStep step : steps)
|
|
|
|
|
invalidated |= this->invalidate_step(step);
|
|
|
|
|
sort_remove_duplicates(osteps);
|
|
|
|
|
for (PrintObjectStep ostep : osteps)
|
2017-05-31 10:55:59 +00:00
|
|
|
|
for (PrintObject *object : this->objects)
|
|
|
|
|
invalidated |= object->invalidate_step(ostep);
|
2014-06-13 09:19:53 +00:00
|
|
|
|
return invalidated;
|
2014-06-10 22:15:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-30 15:17:26 +00:00
|
|
|
|
bool Print::invalidate_step(PrintStep step)
|
2014-06-10 22:15:02 +00:00
|
|
|
|
{
|
2014-06-13 09:19:53 +00:00
|
|
|
|
bool invalidated = this->state.invalidate(step);
|
2017-05-30 15:17:26 +00:00
|
|
|
|
// Propagate to dependent steps.
|
2017-05-31 15:02:23 +00:00
|
|
|
|
//FIXME Why should skirt invalidate brim? Shouldn't it be vice versa?
|
2017-05-25 20:27:53 +00:00
|
|
|
|
if (step == psSkirt)
|
2017-05-30 15:17:26 +00:00
|
|
|
|
invalidated |= this->state.invalidate(psBrim);
|
2014-06-13 09:19:53 +00:00
|
|
|
|
return invalidated;
|
2014-06-10 22:15:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-11-30 19:18:09 +00:00
|
|
|
|
// returns true if an object step is done on all objects
|
|
|
|
|
// and there's at least one object
|
2017-05-30 15:17:26 +00:00
|
|
|
|
bool Print::step_done(PrintObjectStep step) const
|
2014-11-30 19:18:09 +00:00
|
|
|
|
{
|
2017-05-30 15:17:26 +00:00
|
|
|
|
if (this->objects.empty())
|
|
|
|
|
return false;
|
|
|
|
|
for (const PrintObject *object : this->objects)
|
|
|
|
|
if (!object->state.is_done(step))
|
2014-11-30 19:18:09 +00:00
|
|
|
|
return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-03 16:41:09 +00:00
|
|
|
|
// returns 0-based indices of used extruders
|
2017-05-03 16:28:22 +00:00
|
|
|
|
std::vector<unsigned int> Print::object_extruders() const
|
2014-08-03 16:41:09 +00:00
|
|
|
|
{
|
2017-05-03 16:28:22 +00:00
|
|
|
|
std::vector<unsigned int> extruders;
|
2014-08-03 16:41:09 +00:00
|
|
|
|
|
2017-05-30 15:17:26 +00:00
|
|
|
|
for (PrintRegion* region : this->regions) {
|
2015-03-06 20:35:00 +00:00
|
|
|
|
// these checks reflect the same logic used in the GUI for enabling/disabling
|
|
|
|
|
// extruder selection fields
|
2017-05-30 15:17:26 +00:00
|
|
|
|
if (region->config.perimeters.value > 0 || this->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);
|
2014-08-03 16:41:09 +00:00
|
|
|
|
}
|
2015-03-09 18:27:57 +00:00
|
|
|
|
|
2017-05-15 09:32:59 +00:00
|
|
|
|
sort_remove_duplicates(extruders);
|
2015-03-09 18:27:57 +00:00
|
|
|
|
return extruders;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// returns 0-based indices of used extruders
|
2017-05-03 16:28:22 +00:00
|
|
|
|
std::vector<unsigned int> Print::support_material_extruders() const
|
2015-03-09 18:27:57 +00:00
|
|
|
|
{
|
2017-05-03 16:28:22 +00:00
|
|
|
|
std::vector<unsigned int> extruders;
|
2017-01-30 18:56:46 +00:00
|
|
|
|
bool support_uses_current_extruder = false;
|
|
|
|
|
|
2017-05-03 16:28:22 +00:00
|
|
|
|
for (PrintObject *object : this->objects) {
|
|
|
|
|
if (object->has_support_material()) {
|
|
|
|
|
if (object->config.support_material_extruder == 0)
|
2017-01-30 18:56:46 +00:00
|
|
|
|
support_uses_current_extruder = true;
|
|
|
|
|
else
|
2017-05-03 16:28:22 +00:00
|
|
|
|
extruders.push_back(object->config.support_material_extruder - 1);
|
|
|
|
|
if (object->config.support_material_interface_extruder == 0)
|
2017-01-30 18:56:46 +00:00
|
|
|
|
support_uses_current_extruder = true;
|
|
|
|
|
else
|
2017-05-03 16:28:22 +00:00
|
|
|
|
extruders.push_back(object->config.support_material_interface_extruder - 1);
|
2015-03-06 08:56:58 +00:00
|
|
|
|
}
|
2014-08-03 16:41:09 +00:00
|
|
|
|
}
|
2017-01-30 18:56:46 +00:00
|
|
|
|
|
2017-05-03 16:28:22 +00:00
|
|
|
|
if (support_uses_current_extruder)
|
2017-01-30 18:56:46 +00:00
|
|
|
|
// Add all object extruders to the support extruders as it is not know which one will be used to print supports.
|
2017-05-03 16:28:22 +00:00
|
|
|
|
append(extruders, this->object_extruders());
|
2014-08-03 16:41:09 +00:00
|
|
|
|
|
2017-05-15 09:32:59 +00:00
|
|
|
|
sort_remove_duplicates(extruders);
|
2014-08-03 16:41:09 +00:00
|
|
|
|
return extruders;
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-09 18:36:23 +00:00
|
|
|
|
// returns 0-based indices of used extruders
|
2017-05-03 16:28:22 +00:00
|
|
|
|
std::vector<unsigned int> Print::extruders() const
|
2015-03-09 18:36:23 +00:00
|
|
|
|
{
|
2017-05-03 16:28:22 +00:00
|
|
|
|
std::vector<unsigned int> extruders = this->object_extruders();
|
|
|
|
|
append(extruders, this->support_material_extruders());
|
2017-05-15 09:32:59 +00:00
|
|
|
|
sort_remove_duplicates(extruders);
|
2015-03-09 18:36:23 +00:00
|
|
|
|
return extruders;
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-03 16:28:22 +00:00
|
|
|
|
void Print::_simplify_slices(double distance)
|
2014-08-03 16:41:09 +00:00
|
|
|
|
{
|
2017-05-30 18:09:34 +00:00
|
|
|
|
for (PrintObject *object : this->objects) {
|
|
|
|
|
for (Layer *layer : object->layers) {
|
|
|
|
|
layer->slices.simplify(distance);
|
|
|
|
|
for (LayerRegion *layerm : layer->regions)
|
|
|
|
|
layerm->slices.simplify(distance);
|
2014-08-03 16:41:09 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-03 16:28:22 +00:00
|
|
|
|
double Print::max_allowed_layer_height() const
|
2014-08-03 16:41:09 +00:00
|
|
|
|
{
|
2017-05-03 16:28:22 +00:00
|
|
|
|
double nozzle_diameter_max = 0.;
|
|
|
|
|
for (unsigned int extruder_id : this->extruders())
|
|
|
|
|
nozzle_diameter_max = std::max(nozzle_diameter_max, this->config.nozzle_diameter.get_at(extruder_id));
|
|
|
|
|
return nozzle_diameter_max;
|
2014-08-03 16:41:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-31 10:55:59 +00:00
|
|
|
|
// Caller is responsible for supplying models whose objects don't collide
|
|
|
|
|
// and have explicit instance positions.
|
2017-05-03 16:28:22 +00:00
|
|
|
|
void Print::add_model_object(ModelObject* model_object, int idx)
|
2014-11-09 11:25:59 +00:00
|
|
|
|
{
|
2017-05-31 10:55:59 +00:00
|
|
|
|
// 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 this->objects[idx];
|
|
|
|
|
this->objects[idx] = object;
|
|
|
|
|
} else
|
|
|
|
|
this->objects.emplace_back(object);
|
|
|
|
|
// Invalidate all print steps.
|
|
|
|
|
this->invalidate_all_steps();
|
2014-11-09 11:25:59 +00:00
|
|
|
|
|
2017-05-30 15:17:26 +00:00
|
|
|
|
for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) {
|
2017-05-31 10:55:59 +00:00
|
|
|
|
// Get the config applied to this volume.
|
|
|
|
|
PrintRegionConfig config = this->_region_config_from_model_volume(*model_object->volumes[volume_id]);
|
|
|
|
|
// Find an existing print region with the same config.
|
2017-05-30 15:17:26 +00:00
|
|
|
|
size_t region_id = size_t(-1);
|
|
|
|
|
for (size_t i = 0; i < this->regions.size(); ++ i)
|
|
|
|
|
if (config.equals(this->regions[i]->config)) {
|
|
|
|
|
region_id = i;
|
2014-11-09 11:25:59 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
2017-05-31 10:55:59 +00:00
|
|
|
|
// If no region exists with the same config, create a new one.
|
2017-05-30 15:17:26 +00:00
|
|
|
|
if (region_id == size_t(-1)) {
|
|
|
|
|
region_id = this->regions.size();
|
|
|
|
|
this->add_region()->config.apply(config);
|
2014-11-09 11:25:59 +00:00
|
|
|
|
}
|
2017-05-31 10:55:59 +00:00
|
|
|
|
// Assign volume to a region.
|
|
|
|
|
object->add_region_volume(region_id, volume_id);
|
2014-11-09 11:25:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-31 10:55:59 +00:00
|
|
|
|
// Apply config to print object.
|
|
|
|
|
object->config.apply(this->default_object_config);
|
|
|
|
|
normalize_and_apply_config(object->config, model_object->config);
|
2016-12-20 18:26:23 +00:00
|
|
|
|
|
|
|
|
|
// update placeholders
|
|
|
|
|
{
|
|
|
|
|
// get the first input file name
|
|
|
|
|
std::string input_file;
|
|
|
|
|
std::vector<std::string> v_scale;
|
|
|
|
|
for (const PrintObject *object : this->objects) {
|
|
|
|
|
const ModelObject &mobj = *object->model_object();
|
2017-05-31 10:55:59 +00:00
|
|
|
|
v_scale.push_back(boost::lexical_cast<std::string>(mobj.instances[0]->scaling_factor*100) + "%");
|
2016-12-20 18:26:23 +00:00
|
|
|
|
if (input_file.empty())
|
|
|
|
|
input_file = mobj.input_file;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PlaceholderParser &pp = this->placeholder_parser;
|
|
|
|
|
pp.set("scale", v_scale);
|
2017-05-31 10:55:59 +00:00
|
|
|
|
if (! input_file.empty()) {
|
2016-12-20 18:26:23 +00:00
|
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-11-09 11:25:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-30 18:09:34 +00:00
|
|
|
|
bool Print::apply_config(DynamicPrintConfig config)
|
2014-11-09 11:25:59 +00:00
|
|
|
|
{
|
|
|
|
|
// we get a copy of the config object so we can modify it safely
|
|
|
|
|
config.normalize();
|
|
|
|
|
|
|
|
|
|
// apply variables to placeholder parser
|
|
|
|
|
this->placeholder_parser.apply_config(config);
|
|
|
|
|
|
|
|
|
|
// handle changes to print config
|
|
|
|
|
t_config_option_keys print_diff = this->config.diff(config);
|
2017-10-17 14:01:18 +00:00
|
|
|
|
this->config.apply_only(config, print_diff, true);
|
2017-05-31 10:55:59 +00:00
|
|
|
|
bool invalidated = this->invalidate_state_by_config_options(print_diff);
|
2014-11-09 11:25:59 +00:00
|
|
|
|
|
|
|
|
|
// handle changes to object config defaults
|
|
|
|
|
this->default_object_config.apply(config, true);
|
2017-05-30 18:09:34 +00:00
|
|
|
|
for (PrintObject *object : this->objects) {
|
2014-11-09 11:25:59 +00:00
|
|
|
|
// 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
|
2017-05-31 10:55:59 +00:00
|
|
|
|
normalize_and_apply_config(new_config, object->model_object()->config);
|
2017-06-09 11:27:35 +00:00
|
|
|
|
// 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;
|
|
|
|
|
}
|
2014-11-09 11:25:59 +00:00
|
|
|
|
// check whether the new config is different from the current one
|
2017-05-30 18:09:34 +00:00
|
|
|
|
t_config_option_keys diff = object->config.diff(new_config);
|
2017-10-17 14:01:18 +00:00
|
|
|
|
object->config.apply_only(new_config, diff, true);
|
2017-05-30 18:09:34 +00:00
|
|
|
|
invalidated |= object->invalidate_state_by_config_options(diff);
|
2014-11-09 11:25:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// handle changes to regions config defaults
|
|
|
|
|
this->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;
|
2017-05-30 18:09:34 +00:00
|
|
|
|
{
|
2017-05-31 10:55:59 +00:00
|
|
|
|
// 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 < this->regions.size(); ++ region_id) {
|
|
|
|
|
PrintRegion ®ion = *this->regions[region_id];
|
|
|
|
|
PrintRegionConfig this_region_config;
|
|
|
|
|
bool this_region_config_set = false;
|
|
|
|
|
for (PrintObject *object : this->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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-11-09 11:25:59 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-31 10:55:59 +00:00
|
|
|
|
}
|
|
|
|
|
if (this_region_config_set) {
|
|
|
|
|
t_config_option_keys diff = region.config.diff(this_region_config);
|
2017-05-30 18:09:34 +00:00
|
|
|
|
if (! diff.empty()) {
|
2017-10-17 14:01:18 +00:00
|
|
|
|
region.config.apply_only(this_region_config, diff);
|
2017-05-30 18:09:34 +00:00
|
|
|
|
for (PrintObject *object : this->objects)
|
2017-05-31 10:55:59 +00:00
|
|
|
|
if (region_id < object->region_volumes.size() && ! object->region_volumes[region_id].empty())
|
|
|
|
|
invalidated |= object->invalidate_state_by_config_options(diff);
|
2014-11-09 11:25:59 +00:00
|
|
|
|
}
|
2017-09-01 15:30:18 +00:00
|
|
|
|
other_region_configs.emplace_back(std::move(this_region_config));
|
2014-11-09 11:25:59 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-31 10:55:59 +00:00
|
|
|
|
|
|
|
|
|
exit_for_rearrange_regions:
|
2014-11-09 11:25:59 +00:00
|
|
|
|
|
|
|
|
|
if (rearrange_regions) {
|
2017-05-30 18:09:34 +00:00
|
|
|
|
// The current subdivision of regions does not make sense anymore.
|
|
|
|
|
// We need to remove all objects and re-add them.
|
2014-11-09 11:25:59 +00:00
|
|
|
|
ModelObjectPtrs model_objects;
|
2017-05-30 18:09:34 +00:00
|
|
|
|
model_objects.reserve(this->objects.size());
|
|
|
|
|
for (PrintObject *object : this->objects)
|
|
|
|
|
model_objects.push_back(object->model_object());
|
2014-11-09 11:25:59 +00:00
|
|
|
|
this->clear_objects();
|
2017-06-15 18:15:53 +00:00
|
|
|
|
for (ModelObject *mo : model_objects)
|
2017-05-30 18:09:34 +00:00
|
|
|
|
this->add_model_object(mo);
|
2014-11-09 11:25:59 +00:00
|
|
|
|
invalidated = true;
|
|
|
|
|
}
|
2017-06-15 18:15:53 +00:00
|
|
|
|
|
|
|
|
|
// Always make sure that the layer_height_profiles are set, as they should not be modified from the worker threads.
|
|
|
|
|
for (PrintObject *object : this->objects)
|
|
|
|
|
if (! object->layer_height_profile_valid)
|
|
|
|
|
object->update_layer_height_profile();
|
2014-11-09 11:25:59 +00:00
|
|
|
|
|
|
|
|
|
return invalidated;
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-06 08:56:58 +00:00
|
|
|
|
bool Print::has_infinite_skirt() const
|
2014-08-03 16:41:09 +00:00
|
|
|
|
{
|
2015-03-06 08:56:58 +00:00
|
|
|
|
return (this->config.skirt_height == -1 && this->config.skirts > 0)
|
|
|
|
|
|| (this->config.ooze_prevention && this->extruders().size() > 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Print::has_skirt() const
|
|
|
|
|
{
|
|
|
|
|
return (this->config.skirt_height > 0 && this->config.skirts > 0)
|
|
|
|
|
|| this->has_infinite_skirt();
|
2014-08-03 16:41:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-30 18:09:34 +00:00
|
|
|
|
std::string Print::validate() const
|
2014-11-09 14:27:34 +00:00
|
|
|
|
{
|
2018-03-14 15:11:57 +00:00
|
|
|
|
BoundingBox bed_box_2D = get_extents(Polygon::new_scale(config.bed_shape.values));
|
|
|
|
|
BoundingBoxf3 print_volume(Pointf3(unscale(bed_box_2D.min.x), unscale(bed_box_2D.min.y), 0.0), Pointf3(unscale(bed_box_2D.max.x), unscale(bed_box_2D.max.y), config.max_print_height));
|
2018-03-14 17:16:17 +00:00
|
|
|
|
// 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.z = -1e10;
|
2018-03-14 15:11:57 +00:00
|
|
|
|
for (PrintObject *po : this->objects) {
|
|
|
|
|
if (! print_volume.contains(po->model_object()->tight_bounding_box(false)))
|
|
|
|
|
return "Some objects are outside of the print volume.";
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-09 14:27:34 +00:00
|
|
|
|
if (this->config.complete_objects) {
|
2017-05-31 10:55:59 +00:00
|
|
|
|
// Check horizontal clearance.
|
2014-11-09 14:27:34 +00:00
|
|
|
|
{
|
2017-05-31 10:55:59 +00:00
|
|
|
|
Polygons convex_hulls_other;
|
|
|
|
|
for (PrintObject *object : this->objects) {
|
2017-06-13 09:35:24 +00:00
|
|
|
|
// Get convex hull of all meshes assigned to this print object.
|
2015-01-07 20:57:22 +00:00
|
|
|
|
Polygon convex_hull;
|
|
|
|
|
{
|
|
|
|
|
Polygons mesh_convex_hulls;
|
2017-05-31 10:55:59 +00:00
|
|
|
|
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());
|
2015-01-07 20:57:22 +00:00
|
|
|
|
// make a single convex hull for all of them
|
2015-01-19 17:53:04 +00:00
|
|
|
|
convex_hull = Slic3r::Geometry::convex_hull(mesh_convex_hulls);
|
2015-01-07 20:57:22 +00:00
|
|
|
|
}
|
2017-05-31 10:55:59 +00:00
|
|
|
|
// Apply the same transformations we apply to the actual meshes when slicing them.
|
2014-11-09 14:27:34 +00:00
|
|
|
|
object->model_object()->instances.front()->transform_polygon(&convex_hull);
|
2017-05-31 10:55:59 +00:00
|
|
|
|
// Grow convex hull with the clearance margin.
|
2016-12-13 18:22:23 +00:00
|
|
|
|
convex_hull = offset(convex_hull, scale_(this->config.extruder_clearance_radius.value)/2, jtRound, scale_(0.1)).front();
|
2017-05-31 10:55:59 +00:00
|
|
|
|
// Now we check that no instance of convex_hull intersects any of the previously checked object instances.
|
2017-05-03 16:28:22 +00:00
|
|
|
|
for (const Point © : object->_shifted_copies) {
|
2014-11-09 14:27:34 +00:00
|
|
|
|
Polygon p = convex_hull;
|
2017-05-03 16:28:22 +00:00
|
|
|
|
p.translate(copy);
|
2017-05-31 10:55:59 +00:00
|
|
|
|
if (! intersection(convex_hulls_other, p).empty())
|
2016-11-05 01:23:46 +00:00
|
|
|
|
return "Some objects are too close; your extruder will collide with them.";
|
2017-05-31 10:55:59 +00:00
|
|
|
|
polygons_append(convex_hulls_other, p);
|
2014-11-09 14:27:34 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-31 10:55:59 +00:00
|
|
|
|
// Check vertical clearance.
|
2014-11-09 14:27:34 +00:00
|
|
|
|
{
|
|
|
|
|
std::vector<coord_t> object_height;
|
2017-05-31 10:55:59 +00:00
|
|
|
|
for (const PrintObject *object : this->objects)
|
2014-11-09 14:27:34 +00:00
|
|
|
|
object_height.insert(object_height.end(), object->copies().size(), object->size.z);
|
|
|
|
|
std::sort(object_height.begin(), object_height.end());
|
2017-05-31 10:55:59 +00:00
|
|
|
|
// 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.
|
2014-11-09 14:27:34 +00:00
|
|
|
|
object_height.pop_back();
|
2017-05-31 10:55:59 +00:00
|
|
|
|
if (! object_height.empty() && object_height.back() > scale_(this->config.extruder_clearance_height.value))
|
2016-11-05 01:23:46 +00:00
|
|
|
|
return "Some objects are too tall and cannot be printed without extruder collisions.";
|
2014-11-09 14:27:34 +00:00
|
|
|
|
}
|
2016-09-13 11:30:00 +00:00
|
|
|
|
} // end if (this->config.complete_objects)
|
2017-05-31 10:55:59 +00:00
|
|
|
|
|
2014-11-09 14:27:34 +00:00
|
|
|
|
if (this->config.spiral_vase) {
|
|
|
|
|
size_t total_copies_count = 0;
|
2017-05-31 10:55:59 +00:00
|
|
|
|
for (const PrintObject *object : this->objects)
|
|
|
|
|
total_copies_count += object->copies().size();
|
2017-06-23 08:13:09 +00:00
|
|
|
|
// #4043
|
|
|
|
|
if (total_copies_count > 1 && ! this->config.complete_objects.value)
|
2016-11-05 01:23:46 +00:00
|
|
|
|
return "The Spiral Vase option can only be used when printing a single object.";
|
2014-11-09 14:27:34 +00:00
|
|
|
|
if (this->regions.size() > 1)
|
2016-11-05 01:23:46 +00:00
|
|
|
|
return "The Spiral Vase option can only be used when printing single material objects.";
|
2014-11-09 14:27:34 +00:00
|
|
|
|
}
|
2017-06-06 08:36:14 +00:00
|
|
|
|
|
2018-03-29 13:32:09 +00:00
|
|
|
|
if (this->config.single_extruder_multi_material) {
|
|
|
|
|
for (size_t i=1; i<this->config.nozzle_diameter.values.size(); ++i)
|
|
|
|
|
if (this->config.nozzle_diameter.values[i] != this->config.nozzle_diameter.values[i-1])
|
|
|
|
|
return "All extruders must have the same diameter for single extruder multimaterial printer.";
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-17 07:07:18 +00:00
|
|
|
|
if (this->has_wipe_tower() && ! this->objects.empty()) {
|
2017-08-28 12:57:00 +00:00
|
|
|
|
#if 0
|
2017-06-06 08:36:14 +00:00
|
|
|
|
for (auto dmr : this->config.nozzle_diameter.values)
|
|
|
|
|
if (std::abs(dmr - 0.4) > EPSILON)
|
|
|
|
|
return "The Wipe Tower is currently only supported for the 0.4mm nozzle diameter.";
|
2017-08-28 12:57:00 +00:00
|
|
|
|
#endif
|
2018-01-06 17:49:28 +00:00
|
|
|
|
if (this->config.gcode_flavor != gcfRepRap && this->config.gcode_flavor != gcfMarlin)
|
|
|
|
|
return "The Wipe Tower is currently only supported for the Marlin and RepRap/Sprinter G-code flavors.";
|
2017-06-06 08:36:14 +00:00
|
|
|
|
if (! this->config.use_relative_e_distances)
|
|
|
|
|
return "The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).";
|
2017-06-08 14:58:29 +00:00
|
|
|
|
SlicingParameters slicing_params0 = this->objects.front()->slicing_parameters();
|
2018-04-16 09:47:35 +00:00
|
|
|
|
|
|
|
|
|
const PrintObject* most_layered_object = this->objects.front(); // object with highest layer_height_profile.size() encountered so far
|
|
|
|
|
for (const auto* object : objects)
|
|
|
|
|
if (object->layer_height_profile.size() > most_layered_object->layer_height_profile.size())
|
|
|
|
|
most_layered_object = object;
|
|
|
|
|
|
2017-06-06 09:40:35 +00:00
|
|
|
|
for (PrintObject *object : this->objects) {
|
2017-06-08 14:58:29 +00:00
|
|
|
|
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)
|
2017-06-06 09:40:35 +00:00
|
|
|
|
return "The Wipe Tower is only supported for multiple objects if they have equal layer heigths";
|
2017-06-08 14:58:29 +00:00
|
|
|
|
if (slicing_params.raft_layers() != slicing_params0.raft_layers())
|
|
|
|
|
return "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 != this->objects.front()->config.support_material_contact_distance)
|
|
|
|
|
return "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 "The Wipe Tower is only supported for multiple objects if they are sliced equally.";
|
2017-06-09 11:27:35 +00:00
|
|
|
|
bool was_layer_height_profile_valid = object->layer_height_profile_valid;
|
2017-06-06 09:40:35 +00:00
|
|
|
|
object->update_layer_height_profile();
|
2017-06-09 11:27:35 +00:00
|
|
|
|
object->layer_height_profile_valid = was_layer_height_profile_valid;
|
2018-04-03 11:51:12 +00:00
|
|
|
|
|
2018-04-04 07:59:41 +00:00
|
|
|
|
if ( this->config.variable_layer_height ) {
|
|
|
|
|
int i = 0;
|
2018-04-16 09:47:35 +00:00
|
|
|
|
while ( i < object->layer_height_profile.size() ) {
|
|
|
|
|
if (std::abs(most_layered_object->layer_height_profile[i] - object->layer_height_profile[i]) > EPSILON)
|
2018-04-04 07:59:41 +00:00
|
|
|
|
return "The Wipe tower is only supported if all objects have the same layer height profile";
|
|
|
|
|
++i;
|
2018-04-16 09:47:35 +00:00
|
|
|
|
if (i == object->layer_height_profile.size()-2) // this element contains the objects max z, if the other object is taller,
|
|
|
|
|
// it does not have to match - we will step over it
|
|
|
|
|
if (most_layered_object->layer_height_profile[i] > object->layer_height_profile[i])
|
|
|
|
|
++i;
|
2018-04-04 07:59:41 +00:00
|
|
|
|
}
|
2018-04-03 11:51:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-29 13:32:09 +00:00
|
|
|
|
/*for (size_t i = 5; i < object->layer_height_profile.size(); i += 2)
|
2017-06-08 14:58:29 +00:00
|
|
|
|
if (object->layer_height_profile[i-1] > slicing_params.object_print_z_min + EPSILON &&
|
|
|
|
|
std::abs(object->layer_height_profile[i] - object->config.layer_height) > EPSILON)
|
2018-03-29 13:32:09 +00:00
|
|
|
|
return "The Wipe Tower is currently only supported with constant Z layer spacing. Layer editing is not allowed.";*/
|
2017-06-06 09:40:35 +00:00
|
|
|
|
}
|
2017-06-06 08:36:14 +00:00
|
|
|
|
}
|
2014-11-09 14:27:34 +00:00
|
|
|
|
|
|
|
|
|
{
|
2015-03-06 20:35:00 +00:00
|
|
|
|
// find the smallest nozzle diameter
|
2017-05-03 16:28:22 +00:00
|
|
|
|
std::vector<unsigned int> extruders = this->extruders();
|
2015-03-06 20:35:00 +00:00
|
|
|
|
if (extruders.empty())
|
2016-11-05 01:23:46 +00:00
|
|
|
|
return "The supplied settings will cause an empty print.";
|
2015-03-06 20:35:00 +00:00
|
|
|
|
|
2017-05-03 16:28:22 +00:00
|
|
|
|
std::vector<double> nozzle_diameters;
|
|
|
|
|
for (unsigned int extruder_id : extruders)
|
|
|
|
|
nozzle_diameters.push_back(this->config.nozzle_diameter.get_at(extruder_id));
|
2015-03-06 20:35:00 +00:00
|
|
|
|
double min_nozzle_diameter = *std::min_element(nozzle_diameters.begin(), nozzle_diameters.end());
|
2018-03-21 15:01:31 +00:00
|
|
|
|
|
|
|
|
|
unsigned int total_extruders_count = this->config.nozzle_diameter.size();
|
|
|
|
|
for (const auto& extruder_idx : extruders)
|
|
|
|
|
if ( extruder_idx >= total_extruders_count )
|
2018-04-13 11:43:53 +00:00
|
|
|
|
return "One or more object were assigned an extruder that the printer does not have.";
|
2018-03-21 15:01:31 +00:00
|
|
|
|
|
2017-05-31 10:55:59 +00:00
|
|
|
|
for (PrintObject *object : this->objects) {
|
2017-01-30 18:56:46 +00:00
|
|
|
|
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 "Printing with multiple extruders of differing nozzle diameters. "
|
2017-02-11 23:51:53 +00:00
|
|
|
|
"If support is to be printed with the current extruder (support_material_extruder == 0 or support_material_interface_extruder == 0), "
|
2017-01-30 18:56:46 +00:00
|
|
|
|
"all nozzles have to be of the same diameter.";
|
|
|
|
|
}
|
2015-03-06 20:35:00 +00:00
|
|
|
|
|
|
|
|
|
// validate first_layer_height
|
|
|
|
|
double first_layer_height = object->config.get_abs_value("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;
|
2017-01-30 18:56:46 +00:00
|
|
|
|
first_layer_min_nozzle_diameter = (first_layer_extruder == size_t(-1)) ?
|
|
|
|
|
min_nozzle_diameter :
|
|
|
|
|
this->config.nozzle_diameter.get_at(first_layer_extruder);
|
2015-03-06 20:35:00 +00:00
|
|
|
|
} 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)
|
2016-11-05 01:23:46 +00:00
|
|
|
|
return "First layer height can't be greater than nozzle diameter";
|
2015-03-06 20:35:00 +00:00
|
|
|
|
|
|
|
|
|
// validate layer_height
|
|
|
|
|
if (object->config.layer_height.value > min_nozzle_diameter)
|
2016-11-05 01:23:46 +00:00
|
|
|
|
return "Layer height can't be greater than nozzle diameter";
|
2014-11-09 14:27:34 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-11-05 01:23:46 +00:00
|
|
|
|
|
|
|
|
|
return std::string();
|
2014-11-09 14:27:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-12-12 18:14:52 +00:00
|
|
|
|
// the bounding box of objects placed in copies position
|
|
|
|
|
// (without taking skirt/brim/support material into account)
|
2017-05-30 18:09:34 +00:00
|
|
|
|
BoundingBox Print::bounding_box() const
|
2014-12-12 18:14:52 +00:00
|
|
|
|
{
|
|
|
|
|
BoundingBox bb;
|
2017-05-31 10:55:59 +00:00
|
|
|
|
for (const PrintObject *object : this->objects)
|
|
|
|
|
for (Point copy : object->_shifted_copies) {
|
|
|
|
|
bb.merge(copy);
|
|
|
|
|
copy.translate(object->size);
|
|
|
|
|
bb.merge(copy);
|
2014-12-12 18:14:52 +00:00
|
|
|
|
}
|
|
|
|
|
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
|
2017-05-30 18:09:34 +00:00
|
|
|
|
BoundingBox Print::total_bounding_box() const
|
2014-12-12 18:14:52 +00:00
|
|
|
|
{
|
|
|
|
|
// get objects bounding box
|
|
|
|
|
BoundingBox bb = this->bounding_box();
|
|
|
|
|
|
2014-12-12 18:25:50 +00:00
|
|
|
|
// we need to offset the objects bounding box by at least half the perimeters extrusion width
|
|
|
|
|
Flow perimeter_flow = this->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 (this->config.brim_width.value > 0) {
|
2014-12-16 23:45:05 +00:00
|
|
|
|
Flow brim_flow = this->brim_flow();
|
|
|
|
|
extra = std::max(extra, this->config.brim_width.value + brim_flow.width/2);
|
2014-12-12 18:25:50 +00:00
|
|
|
|
}
|
2015-03-06 08:56:58 +00:00
|
|
|
|
if (this->has_skirt()) {
|
|
|
|
|
int skirts = this->config.skirts.value;
|
|
|
|
|
if (skirts == 0 && this->has_infinite_skirt()) skirts = 1;
|
2014-12-16 23:45:05 +00:00
|
|
|
|
Flow skirt_flow = this->skirt_flow();
|
2014-12-12 18:14:52 +00:00
|
|
|
|
extra = std::max(
|
|
|
|
|
extra,
|
2014-12-12 18:25:50 +00:00
|
|
|
|
this->config.brim_width.value
|
|
|
|
|
+ this->config.skirt_distance.value
|
2015-03-06 08:56:58 +00:00
|
|
|
|
+ skirts * skirt_flow.spacing()
|
2014-12-12 18:25:50 +00:00
|
|
|
|
+ skirt_flow.width/2
|
2014-12-12 18:14:52 +00:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (extra > 0)
|
|
|
|
|
bb.offset(scale_(extra));
|
|
|
|
|
|
|
|
|
|
return bb;
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-30 18:09:34 +00:00
|
|
|
|
double Print::skirt_first_layer_height() const
|
2014-12-12 18:14:52 +00:00
|
|
|
|
{
|
|
|
|
|
if (this->objects.empty()) CONFESS("skirt_first_layer_height() can't be called without PrintObjects");
|
|
|
|
|
return this->objects.front()->config.get_abs_value("first_layer_height");
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-30 18:09:34 +00:00
|
|
|
|
Flow Print::brim_flow() const
|
2014-12-16 23:45:05 +00:00
|
|
|
|
{
|
|
|
|
|
ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width;
|
2017-11-09 09:48:06 +00:00
|
|
|
|
if (width.value == 0)
|
|
|
|
|
width = this->regions.front()->config.perimeter_extrusion_width;
|
|
|
|
|
if (width.value == 0)
|
|
|
|
|
width = this->objects.front()->config.extrusion_width;
|
2014-12-16 23:45:05 +00:00
|
|
|
|
|
|
|
|
|
/* 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,
|
|
|
|
|
this->config.nozzle_diameter.get_at(this->regions.front()->config.perimeter_extruder-1),
|
|
|
|
|
this->skirt_first_layer_height(),
|
|
|
|
|
0
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-30 18:09:34 +00:00
|
|
|
|
Flow Print::skirt_flow() const
|
2014-12-12 18:14:52 +00:00
|
|
|
|
{
|
|
|
|
|
ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width;
|
2017-11-09 09:48:06 +00:00
|
|
|
|
if (width.value == 0)
|
|
|
|
|
width = this->regions.front()->config.perimeter_extrusion_width;
|
|
|
|
|
if (width.value == 0)
|
|
|
|
|
width = this->objects.front()->config.extrusion_width;
|
2014-12-12 18:14:52 +00:00
|
|
|
|
|
2014-12-16 23:45:05 +00:00
|
|
|
|
/* 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. */
|
2014-12-12 18:14:52 +00:00
|
|
|
|
return Flow::new_from_config_width(
|
|
|
|
|
frPerimeter,
|
|
|
|
|
width,
|
|
|
|
|
this->config.nozzle_diameter.get_at(this->objects.front()->config.support_material_extruder-1),
|
|
|
|
|
this->skirt_first_layer_height(),
|
|
|
|
|
0
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-30 18:09:34 +00:00
|
|
|
|
PrintRegionConfig Print::_region_config_from_model_volume(const ModelVolume &volume)
|
2014-11-09 11:25:59 +00:00
|
|
|
|
{
|
|
|
|
|
PrintRegionConfig config = this->default_region_config;
|
2017-05-31 10:55:59 +00:00
|
|
|
|
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);
|
2014-11-09 11:25:59 +00:00
|
|
|
|
return config;
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-30 18:09:34 +00:00
|
|
|
|
bool Print::has_support_material() const
|
2014-08-03 16:41:09 +00:00
|
|
|
|
{
|
2017-05-30 18:09:34 +00:00
|
|
|
|
for (const PrintObject *object : this->objects)
|
|
|
|
|
if (object->has_support_material())
|
|
|
|
|
return true;
|
2014-08-03 16:41:09 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 17:29:33 +00:00
|
|
|
|
/* This method assigns extruders to the volumes having a material
|
|
|
|
|
but not having extruders set in the volume config. */
|
2017-05-30 18:09:34 +00:00
|
|
|
|
void Print::auto_assign_extruders(ModelObject* model_object) const
|
2015-12-02 17:29:33 +00:00
|
|
|
|
{
|
|
|
|
|
// only assign extruders if object has more than one volume
|
2017-05-30 18:09:34 +00:00
|
|
|
|
if (model_object->volumes.size() < 2)
|
|
|
|
|
return;
|
2015-12-02 17:29:33 +00:00
|
|
|
|
|
2017-05-30 18:09:34 +00:00
|
|
|
|
// size_t extruders = this->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->material_id().empty() && ! volume->config.has("extruder"))
|
|
|
|
|
volume->config.opt<ConfigOptionInt>("extruder", true)->value = int(volume_id + 1);
|
2015-12-02 17:29:33 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-15 10:05:52 +00:00
|
|
|
|
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.;
|
|
|
|
|
for (const PrintObject *object : this->objects) {
|
|
|
|
|
size_t skirt_layers = this->has_infinite_skirt() ?
|
|
|
|
|
object->layer_count() :
|
|
|
|
|
std::min(size_t(this->config.skirt_height.value), object->layer_count());
|
|
|
|
|
skirt_height_z = std::max(skirt_height_z, object->layers[skirt_layers-1]->print_z);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collect points from all layers contained in skirt height.
|
|
|
|
|
Points points;
|
|
|
|
|
for (const PrintObject *object : this->objects) {
|
|
|
|
|
Points object_points;
|
|
|
|
|
// Get object layers up to skirt_height_z.
|
|
|
|
|
for (const Layer *layer : object->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->_shifted_copies) {
|
|
|
|
|
Points copy_points = object_points;
|
|
|
|
|
for (Point &pt : copy_points)
|
|
|
|
|
pt.translate(shift);
|
|
|
|
|
append(points, copy_points);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (points.size() < 3)
|
|
|
|
|
// At least three points required for a convex hull.
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
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);
|
2017-10-17 17:41:04 +00:00
|
|
|
|
extruders_e_per_mm.push_back(Extruder((unsigned int)extruder_id, &this->config).e_per_mm(mm3_per_mm));
|
2017-02-15 10:05:52 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Number of skirt loops per skirt layer.
|
|
|
|
|
int n_skirts = this->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.
|
|
|
|
|
coord_t distance = scale_(std::max(this->config.skirt_distance.value, this->config.brim_width.value));
|
|
|
|
|
// 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) {
|
|
|
|
|
// Offset the skirt outside.
|
|
|
|
|
distance += coord_t(scale_(spacing));
|
|
|
|
|
// Generate the skirt centerline.
|
2017-03-23 11:35:00 +00:00
|
|
|
|
Polygon loop;
|
|
|
|
|
{
|
|
|
|
|
Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, scale_(0.1));
|
|
|
|
|
Geometry::simplify_polygons(loops, scale_(0.05), &loops);
|
|
|
|
|
loop = loops.front();
|
|
|
|
|
}
|
2017-02-15 10:05:52 +00:00
|
|
|
|
// 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();
|
|
|
|
|
this->skirt.append(eloop);
|
|
|
|
|
if (this->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(loop.length()) * extruders_e_per_mm[extruder_idx];
|
|
|
|
|
if (extruded_length[extruder_idx] < this->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] >= this->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.
|
|
|
|
|
this->skirt.reverse();
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-07 14:40:23 +00:00
|
|
|
|
void Print::_make_brim()
|
|
|
|
|
{
|
|
|
|
|
// Brim is only printed on first layer and uses perimeter extruder.
|
|
|
|
|
Flow flow = this->brim_flow();
|
|
|
|
|
Polygons islands;
|
|
|
|
|
for (PrintObject *object : this->objects) {
|
|
|
|
|
Polygons object_islands;
|
|
|
|
|
for (ExPolygon &expoly : object->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->_shifted_copies.size());
|
|
|
|
|
for (const Point &pt : object->_shifted_copies)
|
|
|
|
|
for (Polygon &poly : object_islands) {
|
|
|
|
|
islands.push_back(poly);
|
|
|
|
|
islands.back().translate(pt);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Polygons loops;
|
|
|
|
|
size_t num_loops = size_t(floor(this->config.brim_width.value / flow.width));
|
|
|
|
|
for (size_t i = 0; i < num_loops; ++ i) {
|
|
|
|
|
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(this->brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()));
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-25 20:27:53 +00:00
|
|
|
|
// Wipe tower support.
|
2017-07-17 07:07:18 +00:00
|
|
|
|
bool Print::has_wipe_tower() const
|
2017-05-25 20:27:53 +00:00
|
|
|
|
{
|
|
|
|
|
return
|
|
|
|
|
this->config.single_extruder_multi_material.value &&
|
|
|
|
|
! this->config.spiral_vase.value &&
|
|
|
|
|
this->config.wipe_tower.value &&
|
|
|
|
|
this->config.nozzle_diameter.values.size() > 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Print::_clear_wipe_tower()
|
|
|
|
|
{
|
|
|
|
|
m_tool_ordering.clear();
|
2017-09-01 15:30:18 +00:00
|
|
|
|
m_wipe_tower_priming.reset(nullptr);
|
2017-05-25 20:27:53 +00:00
|
|
|
|
m_wipe_tower_tool_changes.clear();
|
|
|
|
|
m_wipe_tower_final_purge.reset(nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Print::_make_wipe_tower()
|
|
|
|
|
{
|
|
|
|
|
this->_clear_wipe_tower();
|
|
|
|
|
if (! this->has_wipe_tower())
|
|
|
|
|
return;
|
|
|
|
|
|
2017-09-01 15:30:18 +00:00
|
|
|
|
// Let the ToolOrdering class know there will be initial priming extrusions at the start of the print.
|
|
|
|
|
m_tool_ordering = ToolOrdering(*this, (unsigned int)-1, true);
|
2017-09-04 11:51:05 +00:00
|
|
|
|
if (! m_tool_ordering.has_wipe_tower())
|
2017-05-30 07:25:34 +00:00
|
|
|
|
// Don't generate any wipe tower.
|
2017-05-25 20:27:53 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2017-12-11 16:19:55 +00:00
|
|
|
|
// 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_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 ToolOrdering::LayerTools < = m_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 this->objects.first()->support_layers to insert these new support layers.
|
|
|
|
|
double wipe_tower_new_layer_print_z_first = m_tool_ordering.layer_tools()[idx_begin].print_z;
|
|
|
|
|
SupportLayerPtrs::iterator it_layer = this->objects.front()->support_layers.begin();
|
2018-02-13 10:18:58 +00:00
|
|
|
|
SupportLayerPtrs::iterator it_end = this->objects.front()->support_layers.end();
|
|
|
|
|
for (; it_layer != it_end && (*it_layer)->print_z - EPSILON < wipe_tower_new_layer_print_z_first; ++ it_layer);
|
2017-12-11 16:19:55 +00:00
|
|
|
|
// 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) {
|
|
|
|
|
ToolOrdering::LayerTools < = const_cast<ToolOrdering::LayerTools&>(m_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_tool_ordering.layer_tools()[i-1].print_z;
|
2018-02-13 10:18:58 +00:00
|
|
|
|
//FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway.
|
|
|
|
|
auto *new_layer = new SupportLayer(size_t(-1), this->objects.front(),
|
2017-12-11 16:19:55 +00:00
|
|
|
|
height, lt.print_z, lt.print_z - 0.5 * height);
|
|
|
|
|
it_layer = this->objects.front()->support_layers.insert(it_layer, new_layer);
|
|
|
|
|
++ it_layer;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-20 14:45:11 +00:00
|
|
|
|
// Get wiping matrix to get number of extruders and convert vector<double> to vector<float>:
|
|
|
|
|
std::vector<float> wiping_volumes((this->config.wiping_volumes_matrix.values).begin(),(this->config.wiping_volumes_matrix.values).end());
|
|
|
|
|
|
2017-05-25 20:27:53 +00:00
|
|
|
|
// Initialize the wipe tower.
|
|
|
|
|
WipeTowerPrusaMM wipe_tower(
|
|
|
|
|
float(this->config.wipe_tower_x.value), float(this->config.wipe_tower_y.value),
|
2018-03-22 12:37:01 +00:00
|
|
|
|
float(this->config.wipe_tower_width.value),
|
2018-03-01 15:15:00 +00:00
|
|
|
|
float(this->config.wipe_tower_rotation_angle.value), float(this->config.cooling_tube_retraction.value),
|
|
|
|
|
float(this->config.cooling_tube_length.value), float(this->config.parking_pos_retraction.value),
|
2018-04-16 12:26:57 +00:00
|
|
|
|
float(this->config.extra_loading_move.value), float(this->config.wipe_tower_bridging), wiping_volumes,
|
|
|
|
|
m_tool_ordering.first_extruder());
|
2018-03-20 14:45:11 +00:00
|
|
|
|
|
2017-05-25 20:27:53 +00:00
|
|
|
|
//wipe_tower.set_retract();
|
|
|
|
|
//wipe_tower.set_zhop();
|
|
|
|
|
|
|
|
|
|
// Set the extruder & material properties at the wipe tower object.
|
2018-03-20 14:45:11 +00:00
|
|
|
|
for (size_t i = 0; i < (int)(sqrt(wiping_volumes.size())+EPSILON); ++ i)
|
2017-05-25 20:27:53 +00:00
|
|
|
|
wipe_tower.set_extruder(
|
|
|
|
|
i,
|
|
|
|
|
WipeTowerPrusaMM::parse_material(this->config.filament_type.get_at(i).c_str()),
|
|
|
|
|
this->config.temperature.get_at(i),
|
2018-03-05 09:45:35 +00:00
|
|
|
|
this->config.first_layer_temperature.get_at(i),
|
|
|
|
|
this->config.filament_loading_speed.get_at(i),
|
|
|
|
|
this->config.filament_unloading_speed.get_at(i),
|
2018-03-12 14:41:25 +00:00
|
|
|
|
this->config.filament_toolchange_delay.get_at(i),
|
2018-04-24 11:02:08 +00:00
|
|
|
|
this->config.filament_cooling_moves.get_at(i),
|
|
|
|
|
this->config.filament_cooling_initial_speed.get_at(i),
|
|
|
|
|
this->config.filament_cooling_final_speed.get_at(i),
|
2018-03-29 13:32:09 +00:00
|
|
|
|
this->config.filament_ramming_parameters.get_at(i),
|
|
|
|
|
this->config.nozzle_diameter.get_at(i));
|
2017-05-25 20:27:53 +00:00
|
|
|
|
|
2017-09-12 13:55:38 +00:00
|
|
|
|
// When printing the first layer's wipe tower, the first extruder is expected to be active and primed.
|
|
|
|
|
// Therefore the number of wipe sections at the wipe tower will be (m_tool_ordering.front().extruders-1) at the 1st layer.
|
|
|
|
|
// The following variable is true if the last priming section cannot be squeezed inside the wipe tower.
|
|
|
|
|
bool last_priming_wipe_full = m_tool_ordering.front().extruders.size() > m_tool_ordering.front().wipe_tower_partitions;
|
|
|
|
|
|
2017-09-01 15:30:18 +00:00
|
|
|
|
m_wipe_tower_priming = Slic3r::make_unique<WipeTower::ToolChangeResult>(
|
2018-03-08 15:44:52 +00:00
|
|
|
|
wipe_tower.prime(this->skirt_first_layer_height(), m_tool_ordering.all_extruders(), ! last_priming_wipe_full));
|
2017-09-01 15:30:18 +00:00
|
|
|
|
|
2017-12-21 12:28:26 +00:00
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
{
|
2017-12-21 13:24:47 +00:00
|
|
|
|
unsigned int current_extruder_id = m_tool_ordering.all_extruders().back();
|
2017-12-21 12:28:26 +00:00
|
|
|
|
for (const auto &layer_tools : m_tool_ordering.layer_tools()) { // for all layers
|
|
|
|
|
if (!layer_tools.has_wipe_tower) continue;
|
|
|
|
|
bool first_layer = &layer_tools == &m_tool_ordering.front();
|
2017-12-22 10:26:43 +00:00
|
|
|
|
wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id,false);
|
2017-12-21 12:28:26 +00:00
|
|
|
|
for (const auto extruder_id : layer_tools.extruders) {
|
2017-12-22 10:26:43 +00:00
|
|
|
|
if ((first_layer && extruder_id == m_tool_ordering.all_extruders().back()) || extruder_id != current_extruder_id) {
|
|
|
|
|
wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id, first_layer && extruder_id == m_tool_ordering.all_extruders().back());
|
2017-12-21 12:28:26 +00:00
|
|
|
|
current_extruder_id = extruder_id;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (&layer_tools == &m_tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-05-25 20:27:53 +00:00
|
|
|
|
// Generate the wipe tower layers.
|
|
|
|
|
m_wipe_tower_tool_changes.reserve(m_tool_ordering.layer_tools().size());
|
2017-12-21 12:28:26 +00:00
|
|
|
|
wipe_tower.generate(m_wipe_tower_tool_changes);
|
|
|
|
|
|
2017-12-03 08:43:00 +00:00
|
|
|
|
// Set current_extruder_id to the last extruder primed.
|
2017-12-21 13:24:47 +00:00
|
|
|
|
/*unsigned int current_extruder_id = m_tool_ordering.all_extruders().back();
|
2017-12-21 12:47:33 +00:00
|
|
|
|
|
2017-05-25 20:27:53 +00:00
|
|
|
|
for (const ToolOrdering::LayerTools &layer_tools : m_tool_ordering.layer_tools()) {
|
|
|
|
|
if (! layer_tools.has_wipe_tower)
|
|
|
|
|
// This is a support only layer, or the wipe tower does not reach to this height.
|
|
|
|
|
continue;
|
|
|
|
|
bool first_layer = &layer_tools == &m_tool_ordering.front();
|
|
|
|
|
bool last_layer = &layer_tools == &m_tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0;
|
|
|
|
|
wipe_tower.set_layer(
|
|
|
|
|
float(layer_tools.print_z),
|
2017-06-08 14:58:29 +00:00
|
|
|
|
float(layer_tools.wipe_tower_layer_height),
|
2017-05-25 20:27:53 +00:00
|
|
|
|
layer_tools.wipe_tower_partitions,
|
|
|
|
|
first_layer,
|
|
|
|
|
last_layer);
|
|
|
|
|
std::vector<WipeTower::ToolChangeResult> tool_changes;
|
|
|
|
|
for (unsigned int extruder_id : layer_tools.extruders)
|
2017-12-03 08:43:00 +00:00
|
|
|
|
// Call the wipe_tower.tool_change() at the first layer for the initial extruder
|
|
|
|
|
// to extrude the wipe tower brim,
|
|
|
|
|
if ((first_layer && extruder_id == m_tool_ordering.all_extruders().back()) ||
|
|
|
|
|
// or when an extruder shall be switched.
|
|
|
|
|
extruder_id != current_extruder_id) {
|
2017-05-30 08:51:38 +00:00
|
|
|
|
tool_changes.emplace_back(wipe_tower.tool_change(extruder_id, extruder_id == layer_tools.extruders.back(), WipeTower::PURPOSE_EXTRUDE));
|
2017-05-25 20:27:53 +00:00
|
|
|
|
current_extruder_id = extruder_id;
|
|
|
|
|
}
|
|
|
|
|
if (! wipe_tower.layer_finished()) {
|
|
|
|
|
tool_changes.emplace_back(wipe_tower.finish_layer(WipeTower::PURPOSE_EXTRUDE));
|
|
|
|
|
if (tool_changes.size() > 1) {
|
|
|
|
|
// Merge the two last tool changes into one.
|
|
|
|
|
WipeTower::ToolChangeResult &tc1 = tool_changes[tool_changes.size() - 2];
|
|
|
|
|
WipeTower::ToolChangeResult &tc2 = tool_changes.back();
|
|
|
|
|
if (tc1.end_pos != tc2.start_pos) {
|
|
|
|
|
// Add a travel move from tc1.end_pos to tc2.start_pos.
|
|
|
|
|
char buf[2048];
|
|
|
|
|
sprintf(buf, "G1 X%.3f Y%.3f F7200\n", tc2.start_pos.x, tc2.start_pos.y);
|
|
|
|
|
tc1.gcode += buf;
|
|
|
|
|
}
|
|
|
|
|
tc1.gcode += tc2.gcode;
|
|
|
|
|
append(tc1.extrusions, tc2.extrusions);
|
|
|
|
|
tc1.end_pos = tc2.end_pos;
|
|
|
|
|
tool_changes.pop_back();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m_wipe_tower_tool_changes.emplace_back(std::move(tool_changes));
|
|
|
|
|
if (last_layer)
|
|
|
|
|
break;
|
2017-12-21 12:28:26 +00:00
|
|
|
|
}*/
|
2017-05-25 20:27:53 +00:00
|
|
|
|
|
|
|
|
|
// Unload the current filament over the purge tower.
|
2017-05-30 07:25:34 +00:00
|
|
|
|
coordf_t layer_height = this->objects.front()->config.layer_height.value;
|
|
|
|
|
if (m_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_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_tool_ordering.back().wipe_tower_partitions == 0);
|
|
|
|
|
wipe_tower.set_layer(float(m_tool_ordering.back().print_z), float(layer_height), 0, false, true);
|
2017-05-25 20:27:53 +00:00
|
|
|
|
}
|
|
|
|
|
m_wipe_tower_final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>(
|
2018-03-08 15:44:52 +00:00
|
|
|
|
wipe_tower.tool_change((unsigned int)-1, false));
|
2017-05-25 20:27:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string Print::output_filename()
|
2016-12-20 18:01:51 +00:00
|
|
|
|
{
|
|
|
|
|
this->placeholder_parser.update_timestamp();
|
2017-12-05 14:54:24 +00:00
|
|
|
|
try {
|
|
|
|
|
return this->placeholder_parser.process(this->config.output_filename_format.value, 0);
|
|
|
|
|
} catch (std::runtime_error &err) {
|
|
|
|
|
throw std::runtime_error(std::string("Failed processing of the output_filename_format template.\n") + err.what());
|
|
|
|
|
}
|
2016-12-20 18:01:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-25 20:27:53 +00:00
|
|
|
|
std::string Print::output_filepath(const std::string &path)
|
2016-12-20 18:01:51 +00:00
|
|
|
|
{
|
|
|
|
|
// 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;
|
2017-05-31 10:55:59 +00:00
|
|
|
|
for (const PrintObject *object : this->objects) {
|
|
|
|
|
input_file = object->model_object()->input_file;
|
|
|
|
|
if (! input_file.empty())
|
|
|
|
|
break;
|
2016-12-20 18:01:51 +00:00
|
|
|
|
}
|
2017-10-27 20:49:59 +00:00
|
|
|
|
return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).make_preferred().string();
|
2016-12-20 18:01:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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))
|
2017-10-27 20:49:59 +00:00
|
|
|
|
return (p / this->output_filename()).make_preferred().string();
|
2016-12-20 18:01:51 +00:00
|
|
|
|
|
|
|
|
|
// if we were supplied a file which is not a directory, use it
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-01 15:30:18 +00:00
|
|
|
|
void Print::set_status(int percent, const std::string &message)
|
|
|
|
|
{
|
|
|
|
|
printf("Print::status %d => %s\n", percent, message.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-20 00:36:42 +00:00
|
|
|
|
}
|