diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 9d3d19362e..13408b50f6 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -69,6 +69,8 @@ namespace Slic3r { double value; bool percent; + double get_abs_value(double ratio_over) const { return this->percent ? (ratio_over * this->value / 100) : this->value; } + private: friend class cereal::access; template void serialize(Archive& ar) { ar(this->value); ar(this->percent); } @@ -2265,17 +2267,18 @@ public: bool is_scalar() const { return (int(this->type) & int(coVectorType)) == 0; } template ConfigOption* load_option_from_archive(Archive &archive) const { - if (this->nullable) { - switch (this->type) { - case coFloat: { auto opt = new ConfigOptionFloatNullable(); archive(*opt); return opt; } - case coInt: { auto opt = new ConfigOptionIntNullable(); archive(*opt); return opt; } - case coFloats: { auto opt = new ConfigOptionFloatsNullable(); archive(*opt); return opt; } - case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; } - case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; } - case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; } - default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); - } - } else { + if (this->nullable) { + switch (this->type) { + case coFloat: { auto opt = new ConfigOptionFloatNullable(); archive(*opt); return opt; } + case coInt: { auto opt = new ConfigOptionIntNullable(); archive(*opt); return opt; } + case coFloats: { auto opt = new ConfigOptionFloatsNullable(); archive(*opt); return opt; } + case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; } + case coPercents: { auto opt = new ConfigOptionPercentsNullable(); archive(*opt); return opt; } + case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; } + case coFloatsOrPercents: { auto opt = new ConfigOptionFloatsOrPercentsNullable(); archive(*opt); return opt; } + default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); + } + } else { switch (this->type) { case coFloat: { auto opt = new ConfigOptionFloat(); archive(*opt); return opt; } case coFloats: { auto opt = new ConfigOptionFloats(); archive(*opt); return opt; } @@ -2300,16 +2303,17 @@ public: } template ConfigOption* save_option_to_archive(Archive &archive, const ConfigOption *opt) const { - if (this->nullable) { - switch (this->type) { - case coFloat: archive(*static_cast(opt)); break; - case coInt: archive(*static_cast(opt)); break; - case coFloats: archive(*static_cast(opt)); break; - case coInts: archive(*static_cast(opt)); break; - case coPercents: archive(*static_cast(opt));break; - case coBools: archive(*static_cast(opt)); break; - default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); - } + if (this->nullable) { + switch (this->type) { + case coFloat: archive(*static_cast(opt)); break; + case coInt: archive(*static_cast(opt)); break; + case coFloats: archive(*static_cast(opt)); break; + case coInts: archive(*static_cast(opt)); break; + case coPercents: archive(*static_cast(opt)); break; + case coBools: archive(*static_cast(opt)); break; + case coFloatsOrPercents: archive(*static_cast(opt)); break; + default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); + } } else { switch (this->type) { case coFloat: archive(*static_cast(opt)); break; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 919ae299f8..14f1bc91a7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2311,6 +2311,17 @@ std::pair split_with_seam( } } // namespace GCode +static inline double get_seam_gap_distance_value(const PrintConfig &config, const unsigned extruder_id) +{ + const double nozzle_diameter = config.nozzle_diameter.get_at(extruder_id); + const FloatOrPercent seam_gap_distance_override = config.filament_seam_gap_distance.get_at(extruder_id); + if (!std::isnan(seam_gap_distance_override.value)) { + return seam_gap_distance_override.get_abs_value(nozzle_diameter); + } + + return config.seam_gap_distance.get_abs_value(nozzle_diameter); +} + using GCode::ExtrusionOrder::InstancePoint; struct SmoothPathGenerator @@ -2361,12 +2372,20 @@ struct SmoothPathGenerator // Clip the path to avoid the extruder to get exactly on the first point of the // loop; if polyline was shorter than the clipping distance we'd get a null // polyline, so we discard it in that case. - const auto nozzle_diameter{config.nozzle_diameter.get_at(extruder_id)}; - if (enable_loop_clipping) { + if (const double extrusion_clipping = get_seam_gap_distance_value(config, extruder_id); enable_loop_clipping && extrusion_clipping > 0.) { clip_end( - result, scale_(nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER, + result, + scaled(extrusion_clipping), scaled(GCode::ExtrusionOrder::min_gcode_segment_length) ); + } else if (enable_loop_clipping && extrusion_clipping < 0.) { + // Extend the extrusion slightly after the seam. + const double smooth_path_extension_length = -1. * scaled(extrusion_clipping); + const double smooth_path_extension_cut_length = length(result) - smooth_path_extension_length; + GCode::SmoothPath smooth_path_extension = result; + + clip_end(smooth_path_extension, smooth_path_extension_cut_length, scaled(GCode::ExtrusionOrder::min_gcode_segment_length)); + Slic3r::append(result, smooth_path_extension); } assert(validate_smooth_path(result, !enable_loop_clipping)); @@ -3057,7 +3076,19 @@ std::string GCodeGenerator::extrude_perimeters( perimeter.extrusion_entity, print_instance.object_layer_to_print_id, print_instance.instance_id ); - if (!m_wipe.enabled() && perimeter.extrusion_entity->role().is_external_perimeter() && m_layer != nullptr && m_config.perimeters.value > 1) { + const bool is_extruding{ + !perimeter.smooth_path.empty() + && !perimeter.smooth_path.front().path.empty() + && perimeter.smooth_path.front().path.front().e_fraction > 0 + }; + + if ( + !m_wipe.enabled() + && perimeter.extrusion_entity->role().is_external_perimeter() + && m_layer != nullptr + && m_config.perimeters.value > 1 + && is_extruding + ) { // Only wipe inside if the wipe along the perimeter is disabled. // Make a little move inwards before leaving loop. if (std::optional pt = wipe_hide_seam(perimeter.smooth_path, perimeter.reversed, scale_(EXTRUDER_CONFIG(nozzle_diameter))); pt) { diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 46cd195b79..257716769b 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -131,7 +131,7 @@ Params Placer::get_params(const DynamicPrintConfig &config) { params.staggered_inner_seams = config.opt_bool("staggered_inner_seams"); params.max_nearest_detour = 1.0; - params.rear_tolerance = 0.2; + params.rear_tolerance = 1.0; params.rear_y_offset = 20; params.aligned.jump_visibility_threshold = 0.6; params.max_distance = 5.0; @@ -139,8 +139,8 @@ Params Placer::get_params(const DynamicPrintConfig &config) { params.perimeter.embedding_threshold = 0.5; params.perimeter.painting_radius = 0.1; params.perimeter.simplification_epsilon = 0.001; - params.perimeter.smooth_angle_arm_length = 0.2; - params.perimeter.sharp_angle_arm_length = 0.05; + params.perimeter.smooth_angle_arm_length = 0.5; + params.perimeter.sharp_angle_arm_length = 0.25; params.visibility.raycasting_visibility_samples_count = 30000; params.visibility.fast_decimation_triangle_count_target = 16000; @@ -297,7 +297,7 @@ boost::variant finalize_seam_position( Scarf::Scarf scarf{}; scarf.entire_loop = region->config().scarf_seam_entire_loop; scarf.max_segment_length = region->config().scarf_seam_max_segment_length; - scarf.start_height = region->config().scarf_seam_start_height.get_abs_value(1.0); + scarf.start_height = std::min(region->config().scarf_seam_start_height.get_abs_value(1.0), 1.0); const double offset{scarf.entire_loop ? 0.0 : region->config().scarf_seam_length.value}; const std::optional outter_scarf_start_point{Geometry::offset_along_lines( diff --git a/src/libslic3r/GCode/SeamRear.cpp b/src/libslic3r/GCode/SeamRear.cpp index f17dd79997..727315fa27 100644 --- a/src/libslic3r/GCode/SeamRear.cpp +++ b/src/libslic3r/GCode/SeamRear.cpp @@ -59,16 +59,28 @@ struct RearestPointCalculator { const Vec2d prefered_position{center_x, bounding_box.max.y() + rear_y_offset}; auto [_, line_index, point] = possible_distancer.distance_from_lines_extra(prefered_position); const Vec2d location_at_bb{center_x, bounding_box.max.y()}; - auto [_d, line_index_at_bb, point_at_bb] = possible_distancer.distance_from_lines_extra(location_at_bb); - const double y_distance{point.y() - point_at_bb.y()}; + auto [_d, line_index_at_bb, point_bb] = possible_distancer.distance_from_lines_extra(location_at_bb); + const double y_distance{point.y() - point_bb.y()}; Vec2d result{point}; if (y_distance < 0) { - result = point_at_bb; + result = point_bb; } else if (y_distance <= rear_tolerance) { const double factor{y_distance / rear_tolerance}; - result = factor * point + (1 - factor) * point_at_bb; + result = factor * point + (1 - factor) * point_bb; } + + if (bounding_box.max.y() - result.y() > rear_tolerance) { + for (const PerimeterLine &line : possible_lines) { + if (line.a.y() > result.y()) { + result = line.a; + } + if (line.b.y() > result.y()) { + result = line.b; + } + } + } + return SeamChoice{possible_lines[line_index].previous_index, possible_lines[line_index].next_index, result}; } }; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index d4182df666..1fd87f3e2a 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -456,7 +456,8 @@ static std::vector s_Preset_print_options { "top_solid_layers", "top_solid_min_thickness", "bottom_solid_layers", "bottom_solid_min_thickness", "ensure_vertical_shell_thickness", "extra_perimeters", "extra_perimeters_on_overhangs", "avoid_crossing_curled_overhangs", "avoid_crossing_perimeters", "thin_walls", "overhangs", - "seam_position","staggered_inner_seams", "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern", + "seam_position", "staggered_inner_seams", "seam_gap_distance", + "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern", "scarf_seam_placement", "scarf_seam_only_on_smooth", "scarf_seam_start_height", "scarf_seam_entire_loop", "scarf_seam_length", "scarf_seam_max_segment_length", "scarf_seam_on_inner_perimeters", "infill_every_layers", /*"infill_only_where_needed",*/ "solid_infill_every_layers", "fill_angle", "bridge_angle", "solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", @@ -512,6 +513,8 @@ static std::vector s_Preset_filament_options { "filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits", // Shrinkage compensation "filament_shrinkage_compensation_xy", "filament_shrinkage_compensation_z", + // Seams overrides + "filament_seam_gap_distance" }; static std::vector s_Preset_machine_limits_options { diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index d9db8d0cb0..4da9807adc 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -120,6 +120,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "filament_density", "filament_notes", "filament_cost", + "filament_seam_gap_distance", "filament_spool_weight", "first_layer_acceleration", "first_layer_acceleration_over_raft", @@ -161,6 +162,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "retract_restart_extra", "retract_restart_extra_toolchange", "retract_speed", + "seam_gap_distance", "single_extruder_multi_material_priming", "slowdown_below_layer_time", "solid_infill_acceleration", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 9a33bddcec..885d126e23 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2654,6 +2654,17 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats { 0. }); + def = this->add("seam_gap_distance", coFloatOrPercent); + def->label = L("Seam gap distance"); + def->tooltip = L("The distance between the endpoints of a closed loop perimeter. " + "Positive values will shorten and interrupt the loop slightly to reduce the seam. " + "Negative values will extend the loop, causing the endpoints to overlap slightly. " + "When percents are used, the distance is derived from the nozzle diameter. " + "Set to zero to disable this feature."); + def->sidetext = L("mm or %"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatOrPercent{ 15., true }); + def = this->add("seam_position", coEnum); def->label = L("Seam position"); def->category = L("Layers and Perimeters"); @@ -2699,6 +2710,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Start height of the scarf joint specified as fraction of the current layer height."); def->sidetext = L(" %"); def->min = 0; + def->max = 100; def->mode = comAdvanced; def->set_default_value(new ConfigOptionPercent(0)); @@ -3774,9 +3786,9 @@ void PrintConfigDef::init_fff_params() auto it_opt = options.find(opt_key); assert(it_opt != options.end()); def = this->add_nullable(std::string("filament_") + opt_key, it_opt->second.type); - def->label = it_opt->second.label; + def->label = it_opt->second.label; def->full_label = it_opt->second.full_label; - def->tooltip = it_opt->second.tooltip; + def->tooltip = it_opt->second.tooltip; def->sidetext = it_opt->second.sidetext; def->mode = it_opt->second.mode; switch (def->type) { @@ -3786,6 +3798,44 @@ void PrintConfigDef::init_fff_params() default: assert(false); } } + + // Declare values for filament profile, overriding printer's profile. + for (const char *opt_key : { + // Floats or Percents + "seam_gap_distance"}) { + + auto it_opt = options.find(opt_key); + assert(it_opt != options.end()); + + switch (it_opt->second.type) { + case coFloatOrPercent: { + def = this->add_nullable(std::string("filament_") + opt_key, coFloatsOrPercents); + break; + } + default: { + assert(false); + break; + } + } + + def->label = it_opt->second.label; + def->full_label = it_opt->second.full_label; + def->tooltip = it_opt->second.tooltip; + def->sidetext = it_opt->second.sidetext; + def->mode = it_opt->second.mode; + + switch (def->type) { + case coFloatsOrPercents: { + const auto &default_value = *static_cast(it_opt->second.default_value.get()); + def->set_default_value(new ConfigOptionFloatsOrPercentsNullable{{default_value.value, default_value.percent}}); + break; + } + default: { + assert(false); + break; + } + } + } } void PrintConfigDef::init_extruder_option_keys() diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index da0fa245f1..93bb6747be 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -830,6 +830,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloats, filament_multitool_ramming_flow)) ((ConfigOptionFloats, filament_stamping_loading_speed)) ((ConfigOptionFloats, filament_stamping_distance)) + ((ConfigOptionFloatsOrPercentsNullable, filament_seam_gap_distance)) ((ConfigOptionPercents, filament_shrinkage_compensation_xy)) ((ConfigOptionPercents, filament_shrinkage_compensation_z)) ((ConfigOptionBool, gcode_comments)) @@ -860,6 +861,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloats, retract_restart_extra)) ((ConfigOptionFloats, retract_restart_extra_toolchange)) ((ConfigOptionFloats, retract_speed)) + ((ConfigOptionFloatOrPercent, seam_gap_distance)) ((ConfigOptionString, start_gcode)) ((ConfigOptionStrings, start_filament_gcode)) ((ConfigOptionBool, single_extruder_multi_material)) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index e9e92b37b1..fe00dd229a 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -331,19 +331,22 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true if ((m_opt.type == coFloatOrPercent || m_opt.type == coFloatsOrPercents) && !str.IsEmpty() && str.Last() != '%') { double val = 0.; + + bool is_na_value = m_opt.nullable && str == na_value(); + const char dec_sep = is_decimal_separator_point() ? '.' : ','; const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; // Replace the first incorrect separator in decimal number. - if (str.Replace(dec_sep_alt, dec_sep, false) != 0) + if (!is_na_value && str.Replace(dec_sep_alt, dec_sep, false) != 0) set_value(str, false); - // remove space and "mm" substring, if any exists str.Replace(" ", "", true); str.Replace("m", "", true); - if (!str.ToDouble(&val)) - { + if (is_na_value) { + val = ConfigOptionFloatsOrPercentsNullable::nil_value().value; + } else if (!str.ToDouble(&val)) { if (!check_value) { m_value.clear(); break; @@ -453,8 +456,11 @@ void TextCtrl::BUILD() { case coFloatsOrPercents: { const auto val = m_opt.get_default_value()->get_at(m_opt_idx); text_value = double_to_string(val.value); - if (val.percent) + if (val.percent) { text_value += "%"; + } + + m_last_meaningful_value = text_value; break; } case coPercent: diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 6dae3aa17f..574aac0ab4 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -22,6 +22,7 @@ #include #include #include "slic3r/GUI/Search.hpp" // IWYU pragma: keep +#include "slic3r/GUI/Field.hpp" #include "libslic3r/Exception.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/AppConfig.hpp" @@ -175,8 +176,10 @@ void OptionsGroup::change_opt_value(DynamicPrintConfig& config, const t_config_o str.pop_back(); percent = true; } - double val = std::stod(str); // locale-dependent (on purpose - the input is the actual content of the field) - ConfigOptionFloatsOrPercents* vec_new = new ConfigOptionFloatsOrPercents({ {val, percent} }); + + const bool is_na_value = opt_def->nullable && str == _(L("N/A")); + const FloatOrPercent val = is_na_value ? ConfigOptionFloatsOrPercentsNullable::nil_value() : FloatOrPercent{std::stod(str), percent}; + ConfigOptionFloatsOrPercents *vec_new = new ConfigOptionFloatsOrPercents({val}); config.option(opt_key)->set_at(vec_new, opt_index, opt_index); break; } @@ -1020,6 +1023,21 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config ret = double_to_string(val); } } break; + case coFloatsOrPercents: { + if (config.option(opt_key)->is_nil()) { + ret = _(L("N/A")); + } else { + const auto &config_option = config.option(opt_key)->get_at(idx); + + text_value = double_to_string(config_option.value); + if (config_option.percent) { + text_value += "%"; + } + + ret = text_value; + } + break; + } case coBools: ret = config.option(opt_key)->values[idx]; break; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 8b36bd8cec..ff50474aaf 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1458,6 +1458,7 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Advanced")); optgroup->append_single_option_line("seam_position", category_path + "seam-position"); + optgroup->append_single_option_line("seam_gap_distance", category_path + "seam-gap-distance"); optgroup->append_single_option_line("staggered_inner_seams", category_path + "staggered-inner-seams"); optgroup->append_single_option_line("scarf_seam_placement", category_path + "scarf-seam-placement"); @@ -1968,6 +1969,9 @@ std::vector>> filament_overrides {"Retraction when tool is disabled", { "filament_retract_length_toolchange", "filament_retract_restart_extra_toolchange" + }}, + {"Seams", { + "filament_seam_gap_distance" }} }; diff --git a/tests/fff_print/test_seam_perimeters.cpp b/tests/fff_print/test_seam_perimeters.cpp index 36e4f67b66..245b3b3291 100644 --- a/tests/fff_print/test_seam_perimeters.cpp +++ b/tests/fff_print/test_seam_perimeters.cpp @@ -124,44 +124,48 @@ constexpr const char *to_string(Perimeters::AngleType angle_type) { throw std::runtime_error("Unreachable"); } -void serialize_shell(std::ostream &output, const Shells::Shell &shell) { +void serialize_shells(std::ostream &output, const Shells::Shells<> &shells) { output << "x,y,z,point_type,point_classification,angle_type,layer_index," - "point_index,distance,distance_to_previous,is_degenerate" + "point_index,distance,distance_to_previous,is_degenerate,shell_index" << std::endl; - for (std::size_t perimeter_index{0}; perimeter_index < shell.size(); ++perimeter_index) { - const Shells::Slice<> &slice{shell[perimeter_index]}; - const Perimeters::Perimeter &perimeter{slice.boundary}; - const std::vector &points{perimeter.positions}; + for (std::size_t shell_index{0}; shell_index < shells.size(); ++shell_index) { + const Shells::Shell<> &shell{shells[shell_index]}; + for (std::size_t perimeter_index{0}; perimeter_index < shell.size(); ++perimeter_index) { + const Shells::Slice<> &slice{shell[perimeter_index]}; + const Perimeters::Perimeter &perimeter{slice.boundary}; + const std::vector &points{perimeter.positions}; - double total_distance{0.0}; - for (std::size_t point_index{0}; point_index < perimeter.point_types.size(); ++point_index) { - const Vec3d point{to_3d(points[point_index], perimeter.slice_z)}; - const Perimeters::PointType point_type{perimeter.point_types[point_index]}; - const Perimeters::PointClassification point_classification{ - perimeter.point_classifications[point_index]}; - const Perimeters::AngleType angle_type{perimeter.angle_types[point_index]}; - const std::size_t layer_index{slice.layer_index}; - const std::size_t previous_index{point_index == 0 ? points.size() - 1 : point_index - 1}; - const double distance_to_previous{(points[point_index] - points[previous_index]).norm()}; - total_distance += point_index == 0 ? 0.0 : distance_to_previous; - const double distance{total_distance}; - const bool is_degenerate{perimeter.is_degenerate}; + double total_distance{0.0}; + for (std::size_t point_index{0}; point_index < perimeter.point_types.size(); ++point_index) { + const Vec3d point{to_3d(points[point_index], perimeter.slice_z)}; + const Perimeters::PointType point_type{perimeter.point_types[point_index]}; + const Perimeters::PointClassification point_classification{ + perimeter.point_classifications[point_index]}; + const Perimeters::AngleType angle_type{perimeter.angle_types[point_index]}; + const std::size_t layer_index{slice.layer_index}; + const std::size_t previous_index{point_index == 0 ? points.size() - 1 : point_index - 1}; + const double distance_to_previous{(points[point_index] - points[previous_index]).norm()}; + total_distance += point_index == 0 ? 0.0 : distance_to_previous; + const double distance{total_distance}; + const bool is_degenerate{perimeter.is_degenerate}; - // clang-format off - output - << point.x() << "," - << point.y() << "," - << point.z() << "," - << to_string(point_type) << "," - << to_string(point_classification) << "," - << to_string(angle_type) << "," - << layer_index << "," - << point_index << "," - << distance << "," - << distance_to_previous << "," - << is_degenerate << std::endl; - // clang-format on + // clang-format off + output + << point.x() << "," + << point.y() << "," + << point.z() << "," + << to_string(point_type) << "," + << to_string(point_classification) << "," + << to_string(angle_type) << "," + << layer_index << "," + << point_index << "," + << distance << "," + << distance_to_previous << "," + << is_degenerate << "," + << shell_index << std::endl; + // clang-format on + } } } } @@ -175,6 +179,6 @@ TEST_CASE_METHOD(Test::SeamsFixture, "Create perimeters", "[Seams][SeamPerimeter if constexpr (debug_files) { std::ofstream csv{"perimeters.csv"}; - serialize_shell(csv, shells[0]); + serialize_shells(csv, shells); } }