mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 10:51:22 +00:00
Merge branch 'main' into zaa
This commit is contained in:
@@ -523,6 +523,11 @@ void AppConfig::set_defaults()
|
||||
set_bool("installed_networking", false);
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
if (get("window_buttons_on_left").empty())
|
||||
set_bool("window_buttons_on_left", false);
|
||||
#endif
|
||||
|
||||
// Remove legacy window positions/sizes
|
||||
erase("app", "main_frame_maximized");
|
||||
erase("app", "main_frame_pos");
|
||||
@@ -941,13 +946,13 @@ void AppConfig::save()
|
||||
}
|
||||
boost::nowide::ofstream c;
|
||||
c.open(path_pid, std::ios::out | std::ios::trunc);
|
||||
c << std::setw(4) << j << std::endl;
|
||||
c << j.dump(1, '\t') << std::endl;
|
||||
|
||||
#ifdef WIN32
|
||||
// WIN32 specific: The final "rename_file()" call is not safe in case of an application crash, there is no atomic "rename file" API
|
||||
// provided by Windows (sic!). Therefore we save a MD5 checksum to be able to verify file corruption. In addition,
|
||||
// we save the config file into a backup first before moving it to the final destination.
|
||||
c << appconfig_md5_hash_line(j.dump(4));
|
||||
c << appconfig_md5_hash_line(j.dump(1, '\t'));
|
||||
#endif
|
||||
|
||||
c.close();
|
||||
|
||||
@@ -54,6 +54,12 @@ WallToolPathsParams make_paths_params(const int layer_id, const PrintObjectConfi
|
||||
input_params.wall_distribution_count = print_object_config.wall_distribution_count.value;
|
||||
|
||||
input_params.is_top_or_bottom_layer = false; // Set to default value
|
||||
|
||||
if (const auto& wall_maximum_resolution_opt = print_object_config.wall_maximum_resolution)
|
||||
input_params.wall_maximum_resolution = scaled<coord_t>(wall_maximum_resolution_opt.value);
|
||||
|
||||
if (const auto& wall_maximum_deviation_opt = print_object_config.wall_maximum_deviation)
|
||||
input_params.wall_maximum_deviation = scaled<coord_t>(wall_maximum_deviation_opt.value);
|
||||
}
|
||||
|
||||
return input_params;
|
||||
@@ -125,7 +131,11 @@ void simplify(Polygon &thiss, const int64_t smallest_line_segment_squared, const
|
||||
accumulated_area_removed += removed_area_next;
|
||||
|
||||
const int64_t length2 = (current - previous).cast<int64_t>().squaredNorm();
|
||||
if (length2 < scaled<int64_t>(25.)) {
|
||||
|
||||
// Orca:
|
||||
// Checking if the segment's length is smaller than 5 microns (0.005mm).
|
||||
// The value of `length2` is scaled and squared, so we need to compare it with the squared value of 5 microns
|
||||
if (length2 < Slic3r::sqr(scaled<coord_t>(0.005))) {
|
||||
// We're allowed to always delete segments of less than 5 micron.
|
||||
continue;
|
||||
}
|
||||
@@ -143,6 +153,7 @@ void simplify(Polygon &thiss, const int64_t smallest_line_segment_squared, const
|
||||
//h^2 = (L / b)^2 [square it]
|
||||
//h^2 = L^2 / b^2 [factor the divisor]
|
||||
const int64_t height_2 = double(area_removed_so_far) * double(area_removed_so_far) / double(base_length_2);
|
||||
// Orca: The value of `height_2` is squared, so we need to compare it with the squared value
|
||||
if ((height_2 <= Slic3r::sqr(scaled<coord_t>(0.005)) //Almost exactly colinear (barring rounding errors).
|
||||
&& Line::distance_to_infinite(current, previous, next) <= scaled<double>(0.005))) // make sure that height_2 is not small because of cancellation of positive and negative areas
|
||||
continue;
|
||||
@@ -473,8 +484,8 @@ const std::vector<VariableWidthLines> &WallToolPaths::generate()
|
||||
if (this->inset_count < 1)
|
||||
return toolpaths;
|
||||
|
||||
const coord_t smallest_segment = Slic3r::Arachne::meshfix_maximum_resolution();
|
||||
const coord_t allowed_distance = Slic3r::Arachne::meshfix_maximum_deviation();
|
||||
const coord_t smallest_segment = m_params.wall_maximum_resolution;
|
||||
const coord_t allowed_distance = m_params.wall_maximum_deviation;
|
||||
const coord_t epsilon_offset = (allowed_distance / 2) - 1;
|
||||
const double transitioning_angle = Geometry::deg2rad(m_params.wall_transition_angle);
|
||||
const coord_t discretization_step_size = scaled<coord_t>(0.8);
|
||||
@@ -547,7 +558,7 @@ const std::vector<VariableWidthLines> &WallToolPaths::generate()
|
||||
|
||||
separateOutInnerContour();
|
||||
|
||||
simplifyToolPaths(toolpaths);
|
||||
simplifyToolPaths(toolpaths, m_params);
|
||||
|
||||
removeEmptyToolPaths(toolpaths);
|
||||
assert(std::is_sorted(toolpaths.cbegin(), toolpaths.cend(),
|
||||
@@ -688,16 +699,21 @@ void WallToolPaths::removeSmallLines(std::vector<VariableWidthLines> &toolpaths)
|
||||
}
|
||||
}
|
||||
|
||||
void WallToolPaths::simplifyToolPaths(std::vector<VariableWidthLines> &toolpaths)
|
||||
void WallToolPaths::simplifyToolPaths(std::vector<VariableWidthLines>& toolpaths, const WallToolPathsParams& params)
|
||||
{
|
||||
for (size_t toolpaths_idx = 0; toolpaths_idx < toolpaths.size(); ++toolpaths_idx)
|
||||
const int64_t maximum_resolution = params.wall_maximum_resolution;
|
||||
const int64_t maximum_deviation = params.wall_maximum_deviation;
|
||||
|
||||
const int64_t smallest_line_segment_squared = maximum_resolution * maximum_resolution;
|
||||
const int64_t allowed_error_distance_squared = maximum_deviation * maximum_deviation;
|
||||
|
||||
const int64_t maximum_extrusion_area_deviation = Slic3r::Arachne::meshfix_maximum_extrusion_area_deviation(); // unit: μm²
|
||||
|
||||
for (VariableWidthLines& lines : toolpaths)
|
||||
{
|
||||
const int64_t maximum_resolution = Slic3r::Arachne::meshfix_maximum_resolution();
|
||||
const int64_t maximum_deviation = Slic3r::Arachne::meshfix_maximum_deviation();
|
||||
const int64_t maximum_extrusion_area_deviation = Slic3r::Arachne::meshfix_maximum_extrusion_area_deviation(); // unit: μm²
|
||||
for (auto& line : toolpaths[toolpaths_idx])
|
||||
for (ExtrusionLine& line : lines)
|
||||
{
|
||||
line.simplify(maximum_resolution * maximum_resolution, maximum_deviation * maximum_deviation, maximum_extrusion_area_deviation);
|
||||
line.simplify(smallest_line_segment_squared, allowed_error_distance_squared, maximum_extrusion_area_deviation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
constexpr bool fill_outline_gaps = true;
|
||||
constexpr bool fill_outline_gaps = true;
|
||||
inline coord_t meshfix_maximum_resolution() { return scaled<coord_t>(0.5); }
|
||||
inline coord_t meshfix_maximum_deviation() { return scaled<coord_t>(0.025); }
|
||||
inline coord_t meshfix_maximum_extrusion_area_deviation() { return scaled<coord_t>(2.); }
|
||||
@@ -31,6 +31,9 @@ public:
|
||||
float wall_transition_filter_deviation;
|
||||
int wall_distribution_count;
|
||||
bool is_top_or_bottom_layer;
|
||||
|
||||
coord_t wall_maximum_resolution = meshfix_maximum_resolution();
|
||||
coord_t wall_maximum_deviation = meshfix_maximum_deviation();
|
||||
};
|
||||
|
||||
WallToolPathsParams make_paths_params(const int layer_id, const PrintObjectConfig &print_object_config, const PrintConfig &print_config);
|
||||
@@ -116,10 +119,11 @@ protected:
|
||||
/*!
|
||||
* Simplifies the variable-width toolpaths by calling the simplify on every line in the toolpath using the provided
|
||||
* settings.
|
||||
* \param settings The settings as provided by the user
|
||||
* \param toolpaths The toolpaths vector to simplify
|
||||
* \param params The settings as provided by the user
|
||||
* \return
|
||||
*/
|
||||
static void simplifyToolPaths(std::vector<VariableWidthLines> &toolpaths);
|
||||
static void simplifyToolPaths(std::vector<VariableWidthLines>& toolpaths, const WallToolPathsParams& params);
|
||||
|
||||
private:
|
||||
const Polygons& outline; //<! A reference to the outline polygon that is the designated area
|
||||
|
||||
@@ -106,7 +106,11 @@ void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const
|
||||
accumulated_area_removed += removed_area_next;
|
||||
|
||||
const int64_t length2 = (current - previous).cast<int64_t>().squaredNorm();
|
||||
if (length2 < scaled<coord_t>(0.025))
|
||||
|
||||
// Orca:
|
||||
// Checking if the segment's length is smaller than 5 microns (0.005mm).
|
||||
// The value of `length2` is scaled and squared, so we need to compare it with the squared value of 5 microns
|
||||
if (length2 < Slic3r::sqr(scaled<coord_t>(0.005)))
|
||||
{
|
||||
// We're allowed to always delete segments of less than 5 micron. The width in this case doesn't matter that much.
|
||||
continue;
|
||||
@@ -128,8 +132,9 @@ void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const
|
||||
//h^2 = L^2 / b^2 [factor the divisor]
|
||||
const auto height_2 = int64_t(double(area_removed_so_far) * double(area_removed_so_far) / double(base_length_2));
|
||||
const int64_t extrusion_area_error = calculateExtrusionAreaDeviationError(previous, current, next);
|
||||
if ((height_2 <= scaled<coord_t>(0.001) //Almost exactly colinear (barring rounding errors).
|
||||
&& Line::distance_to_infinite(current.p, previous.p, next.p) <= scaled<double>(0.001)) // Make sure that height_2 is not small because of cancellation of positive and negative areas
|
||||
// Orca: The value of `height_2` is squared, so we need to compare it with the squared value
|
||||
if ((height_2 <= Slic3r::sqr(scaled<coord_t>(0.005)) // Almost exactly colinear (barring rounding errors).
|
||||
&& Line::distance_to_infinite(current.p, previous.p, next.p) <= scaled<double>(0.005)) // Make sure that height_2 is not small because of cancellation of positive and negative areas
|
||||
// We shouldn't remove middle junctions of colinear segments if the area changed for the C-P segment is exceeding the maximum allowed
|
||||
&& extrusion_area_error <= maximum_extrusion_area_deviation)
|
||||
{
|
||||
|
||||
@@ -527,7 +527,16 @@ bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const Boun
|
||||
build_volume.max.z() = std::numeric_limits<double>::max();
|
||||
if (ignore_bottom)
|
||||
build_volume.min.z() = -std::numeric_limits<double>::max();
|
||||
return build_volume.contains(paths_bbox);
|
||||
// BBox-only callers may provide no moves. Validate bbox corners regardless of paths_bbox.defined.
|
||||
if (paths.moves.empty())
|
||||
return build_volume.contains(paths_bbox.min) && build_volume.contains(paths_bbox.max);
|
||||
if (paths_bbox.defined && build_volume.contains(paths_bbox))
|
||||
return true;
|
||||
|
||||
// Fallback: validate only relevant extrusion moves.
|
||||
const BoundingBox3Base<Vec3f> build_volumef(build_volume.min.cast<float>(), build_volume.max.cast<float>());
|
||||
return std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, build_volumef](const GCodeProcessorResult::MoveVertex &move)
|
||||
{ return !move_valid(move) || build_volumef.contains(move.position); });
|
||||
}
|
||||
case BuildVolume_Type::Circle:
|
||||
{
|
||||
|
||||
@@ -1498,7 +1498,7 @@ void ConfigBase::save_to_json(const std::string &file, const std::string &name,
|
||||
|
||||
boost::nowide::ofstream c;
|
||||
c.open(file, std::ios::out | std::ios::trunc);
|
||||
c << std::setw(4) << j << std::endl;
|
||||
c << j.dump(1, '\t') << std::endl;
|
||||
c.close();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", saved config to %1%\n")%file;
|
||||
|
||||
@@ -199,6 +199,7 @@ enum ConfigOptionType {
|
||||
enum ConfigOptionMode {
|
||||
comSimple = 0,
|
||||
comAdvanced,
|
||||
comExpert,
|
||||
comDevelop,
|
||||
};
|
||||
|
||||
|
||||
@@ -2559,6 +2559,21 @@ void priv::create_face_types(FaceTypeMap &map,
|
||||
bool priv::clip_cut(SurfacePatch &cut, CutMesh clipper)
|
||||
{
|
||||
CutMesh& tm = cut.mesh;
|
||||
auto is_mesh_usable_for_clip = [](const CutMesh &mesh) {
|
||||
return !mesh.is_empty() &&
|
||||
mesh.number_of_vertices() >= 3 &&
|
||||
mesh.number_of_faces() > 0 &&
|
||||
mesh.is_valid(false);
|
||||
};
|
||||
|
||||
if (!is_mesh_usable_for_clip(tm) || !is_mesh_usable_for_clip(clipper))
|
||||
return false;
|
||||
|
||||
// Hard-stop pathological inputs before entering corefinement internals.
|
||||
if (CGAL::Polygon_mesh_processing::does_self_intersect(tm) ||
|
||||
CGAL::Polygon_mesh_processing::does_self_intersect(clipper))
|
||||
return false;
|
||||
|
||||
// create backup for case that there is no intersection
|
||||
CutMesh backup_copy = tm;
|
||||
|
||||
|
||||
@@ -443,9 +443,9 @@ namespace Emboss
|
||||
/// Sample slice polygon by bounding boxes centers
|
||||
/// slice start point has shape_center_x coor
|
||||
/// </summary>
|
||||
/// <param name="slice">Polygon and start point[Slic3r scaled milimeters]</param>
|
||||
/// <param name="slice">Polygon and start point[Slic3r scaled millimeters]</param>
|
||||
/// <param name="bbs">Bounding boxes of letter on one line[in font scales]</param>
|
||||
/// <param name="scale">Scale for bbs (after multiply bb is in milimeters)</param>
|
||||
/// <param name="scale">Scale for bbs (after multiply bb is in millimeters)</param>
|
||||
/// <returns>Sampled polygon by bounding boxes</returns>
|
||||
PolygonPoints sample_slice(const TextLine &slice, const BoundingBoxes &bbs, double scale);
|
||||
|
||||
|
||||
@@ -808,8 +808,10 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n
|
||||
return;
|
||||
}
|
||||
|
||||
// Expand the normal infills a little bit to avoid gaps between normal and narrow infills
|
||||
normal_infill = intersection_ex(offset_ex(normal_fill_areas_ex, scaled_spacing * 0.1), fill.expolygons);
|
||||
// Expand the normal infills to avoid gaps between normal and narrow infills.
|
||||
// The inner_area was shrunk by scaled_spacing * 0.5, so we need to expand
|
||||
// by at least that amount to ensure proper coverage and avoid gaps.
|
||||
normal_infill = intersection_ex(offset_ex(normal_fill_areas_ex, scaled_spacing * 0.5), fill.expolygons);
|
||||
narrow_infill = narrow_fill_areas;
|
||||
|
||||
#ifdef DEBUG_SURFACE_SPLIT
|
||||
@@ -1319,7 +1321,14 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
params.density = f->print_object_config->internal_bridge_density.get_abs_value(1.0);
|
||||
params.dont_adjust = true;
|
||||
}
|
||||
// BBS: make fill
|
||||
// Orca: Elefant foot compensation for solid layers above bottommost by infill density manipulation.
|
||||
float elefant_density = f->print_object_config->elefant_foot_layers_density.get_abs_value(1.0);
|
||||
if (!is_approx(elefant_density, 1.0f) && surface_fill.surface.is_solid_infill()) {
|
||||
size_t elefant_layers = f->print_object_config->elefant_foot_compensation_layers.value;
|
||||
if (f->layer_id > 0 && f->layer_id <= elefant_layers)
|
||||
params.density = elefant_density * (elefant_layers - (f->layer_id - 1)) / elefant_layers;
|
||||
}
|
||||
// make fill
|
||||
f->fill_surface_extrusion(&surface_fill.surface,
|
||||
params,
|
||||
m_regions[surface_fill.region_id]->fills.entities);
|
||||
|
||||
@@ -3082,6 +3082,7 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
|
||||
// Use extended object bounding box for consistent pattern across layers
|
||||
BoundingBox bb = this->extended_object_bounding_box();
|
||||
const size_t infill_layer_id = (surface->thickness_layers > 0) ? this->layer_id / surface->thickness_layers : this->layer_id;
|
||||
|
||||
switch (Pattern_type) {
|
||||
case 0: // Grid / Trapezoidal
|
||||
@@ -3144,8 +3145,8 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
flip_vertical = !flip_vertical;
|
||||
}
|
||||
|
||||
// transpose points for odd layers
|
||||
if (layer_id % 2 == 1) {
|
||||
// transpose points for odd infill layers (taking infill combination into account)
|
||||
if (infill_layer_id % 2 == 1) {
|
||||
for (Polyline& pl : polylines) {
|
||||
for (Point& p : pl.points) {
|
||||
std::swap(p.x(), p.y());
|
||||
@@ -3174,7 +3175,7 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
|
||||
// Align bounding box to the grid
|
||||
bb.merge(align_to_grid(bb.center(), Point(period,h)));
|
||||
const int layer_mod = layer_id % 3;
|
||||
const size_t layer_mod = infill_layer_id % 3;
|
||||
const double angle = layer_mod * 2.0 * M_PI / 3.0;
|
||||
|
||||
const Point rotation_center = bb.center();
|
||||
|
||||
@@ -172,7 +172,10 @@ const std::string BBL_LICENSE_TAG = "License";
|
||||
const std::string BBL_REGION_TAG = "Region";
|
||||
const std::string BBL_MODIFICATION_TAG = "ModificationDate";
|
||||
const std::string BBL_CREATION_DATE_TAG = "CreationDate";
|
||||
// Orca: BBL current version
|
||||
const std::string BBL_APPLICATION_TAG = "Application";
|
||||
// OrcaSlicer version tag
|
||||
const std::string ORCASLICER_TAG = "OrcaSlicer";
|
||||
const std::string BBL_MAKERLAB_TAG = "MakerLab";
|
||||
const std::string BBL_MAKERLAB_VERSION_TAG = "MakerLabVersion";
|
||||
|
||||
@@ -313,6 +316,7 @@ static constexpr const char* TRANSFORM_ATTR = "transform";
|
||||
// BBS
|
||||
static constexpr const char* OFFSET_ATTR = "offset";
|
||||
static constexpr const char* PRINTABLE_ATTR = "printable";
|
||||
static constexpr const char* AUTO_DROP_ATTR = "auto_drop";
|
||||
static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count";
|
||||
static constexpr const char* CUSTOM_SUPPORTS_ATTR = "paint_supports";
|
||||
static constexpr const char* CUSTOM_FUZZY_SKIN_ATTR = "paint_fuzzy_skin";
|
||||
@@ -1030,6 +1034,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
std::string m_origin_file;
|
||||
// Semantic version of Orca Slicer, that generated this 3MF.
|
||||
boost::optional<Semver> m_bambuslicer_generator_version;
|
||||
// Semantic version from the OrcaSlicer metadata tag (if present).
|
||||
boost::optional<Semver> m_orca_slicer_version;
|
||||
unsigned int m_fdm_supports_painting_version = 0;
|
||||
unsigned int m_seam_painting_version = 0;
|
||||
unsigned int m_mm_painting_version = 0;
|
||||
@@ -1101,7 +1107,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
//BBS: add plate data related logic
|
||||
// add backup & restore logic
|
||||
bool load_model_from_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector<Preset*>& project_presets, DynamicPrintConfig& config,
|
||||
ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool* is_bbl_3mf, Semver& file_version, Import3mfProgressFn proFn = nullptr, BBLProject *project = nullptr, int plate_id = 0);
|
||||
ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool* is_bbl_3mf, bool* is_orca_3mf, Semver& file_version, Import3mfProgressFn proFn = nullptr, BBLProject *project = nullptr, int plate_id = 0);
|
||||
bool get_thumbnail(const std::string &filename, std::string &data);
|
||||
bool load_gcode_3mf_from_stream(std::istream & data, Model& model, PlateDataPtrs& plate_data_list, DynamicPrintConfig& config, Semver& file_version);
|
||||
unsigned int version() const { return m_version; }
|
||||
@@ -1204,7 +1210,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
bool _handle_start_text_configuration(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_start_shape_configuration(const char **attributes, unsigned int num_attributes);
|
||||
|
||||
bool _create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter);
|
||||
bool _create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, const bool auto_drop, unsigned int recur_counter);
|
||||
|
||||
void _apply_transform(ModelInstance& instance, const Transform3d& transform);
|
||||
|
||||
@@ -1309,7 +1315,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
//BBS: add plate data related logic
|
||||
// add backup & restore logic
|
||||
bool _BBS_3MF_Importer::load_model_from_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector<Preset*>& project_presets, DynamicPrintConfig& config,
|
||||
ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool* is_bbl_3mf, Semver& file_version, Import3mfProgressFn proFn, BBLProject *project, int plate_id)
|
||||
ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool* is_bbl_3mf, bool* is_orca_3mf, Semver& file_version, Import3mfProgressFn proFn, BBLProject *project, int plate_id)
|
||||
{
|
||||
m_version = 0;
|
||||
m_fdm_supports_painting_version = 0;
|
||||
@@ -1365,8 +1371,18 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
if (is_bbl_3mf) {
|
||||
*is_bbl_3mf = m_is_bbl_3mf;
|
||||
}
|
||||
if (m_bambuslicer_generator_version)
|
||||
file_version = *m_bambuslicer_generator_version;
|
||||
// If the OrcaSlicer tag is present, use it as file_version (ignoring the Bambu Application version).
|
||||
// Otherwise fall back to the version parsed from the Application tag.
|
||||
if (m_orca_slicer_version) {
|
||||
file_version = *m_orca_slicer_version;
|
||||
if (is_orca_3mf)
|
||||
*is_orca_3mf = true;
|
||||
} else {
|
||||
if (m_bambuslicer_generator_version)
|
||||
file_version = *m_bambuslicer_generator_version;
|
||||
if (is_orca_3mf)
|
||||
*is_orca_3mf = false;
|
||||
}
|
||||
// save for restore
|
||||
if (result && m_load_aux && !m_load_restore) {
|
||||
save_string_file(model.get_backup_path() + "/origin.txt", filename);
|
||||
@@ -3766,8 +3782,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
std::string path = bbs_get_attribute_value_string(attributes, num_attributes, PPATH_ATTR);
|
||||
Transform3d transform = bbs_get_transform_from_3mf_specs_string(bbs_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
|
||||
int printable = bbs_get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR);
|
||||
int auto_drop = bbs_get_attribute_value_bool(attributes, num_attributes, AUTO_DROP_ATTR);
|
||||
|
||||
return !m_load_model || _create_object_instance(path, object_id, transform, printable, 1);
|
||||
return !m_load_model || _create_object_instance(path, object_id, transform, printable, auto_drop, 1);
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_end_item()
|
||||
@@ -3816,6 +3833,12 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
m_is_bbl_3mf = true;
|
||||
m_bambuslicer_generator_version = Semver::parse(m_curr_characters.substr(11));
|
||||
}
|
||||
} else if (m_curr_metadata_name == ORCASLICER_TAG) {
|
||||
// OrcaSlicer version tag (written from OrcaSlicer 2.3.2 onwards)
|
||||
m_orca_slicer_version = Semver::parse(m_curr_characters);
|
||||
if (m_orca_slicer_version) {
|
||||
m_is_bbl_3mf = true;
|
||||
}
|
||||
//TODO: currently use version 0, no need to load&&save this string
|
||||
/*} else if (m_curr_metadata_name == BBS_FDM_SUPPORTS_PAINTING_VERSION) {
|
||||
m_fdm_supports_painting_version = (unsigned int) atoi(m_curr_characters.c_str());
|
||||
@@ -3989,7 +4012,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter)
|
||||
bool _BBS_3MF_Importer::_create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, const bool auto_drop, unsigned int recur_counter)
|
||||
{
|
||||
static const unsigned int MAX_RECURSIONS = 10;
|
||||
|
||||
@@ -4026,6 +4049,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
return false;
|
||||
}
|
||||
instance->printable = printable;
|
||||
instance->auto_drop = auto_drop;
|
||||
|
||||
m_instances.emplace_back(instance, transform);
|
||||
|
||||
@@ -4058,6 +4082,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
return false;
|
||||
}
|
||||
instance->printable = printable;
|
||||
instance->auto_drop = auto_drop;
|
||||
|
||||
m_instances.emplace_back(instance, transform);
|
||||
}
|
||||
@@ -5614,12 +5639,14 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
unsigned int id;
|
||||
Transform3d transform;
|
||||
bool printable;
|
||||
bool auto_drop;
|
||||
|
||||
BuildItem(std::string const & path, unsigned int id, const Transform3d& transform, const bool printable)
|
||||
BuildItem(std::string const & path, unsigned int id, const Transform3d& transform, const bool printable, const bool auto_drop)
|
||||
: path(path)
|
||||
, id(id)
|
||||
, transform(transform)
|
||||
, printable(printable)
|
||||
, auto_drop(auto_drop)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -6652,8 +6679,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
// Orca: PRIVACY: do not store creation & modification date in 3mf
|
||||
metadata_item_map[BBL_CREATION_DATE_TAG] = "";
|
||||
metadata_item_map[BBL_MODIFICATION_TAG] = "";
|
||||
//SoftFever: write BambuStudio tag to keep it compatible
|
||||
metadata_item_map[BBL_APPLICATION_TAG] = (boost::format("%1%-%2%") % "BambuStudio" % SoftFever_VERSION).str();
|
||||
// Orca: Write the BambuStudio compatibility version string using SLIC3R_VERSION
|
||||
metadata_item_map[BBL_APPLICATION_TAG] = (boost::format("%1%-%2%") % "BambuStudio" % SLIC3R_VERSION).str();
|
||||
}
|
||||
metadata_item_map[BBS_3MF_VERSION] = std::to_string(VERSION_BBS_3MF);
|
||||
|
||||
@@ -6678,6 +6705,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
BOOST_LOG_TRIVIAL(info) << "bbs_3mf: save key= " << item.first << ", value = " << item.second;
|
||||
stream << " <" << METADATA_TAG << " name=\"" << item.first << "\">"
|
||||
<< xml_escape(item.second) << "</" << METADATA_TAG << ">\n";
|
||||
if (item.first == BBL_APPLICATION_TAG) {
|
||||
stream << " <" << METADATA_TAG << " name=\"" << ORCASLICER_TAG << "\">"
|
||||
<< xml_escape(SoftFever_VERSION) << "</" << METADATA_TAG << ">\n";
|
||||
}
|
||||
}
|
||||
|
||||
stream << " <" << RESOURCES_TAG << ">\n";
|
||||
@@ -6784,7 +6815,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
Transform3d t = instance->get_matrix();
|
||||
// instance_id is just a 1 indexed index in build_items.
|
||||
//assert(m_skip_static || curr_id == build_items.size() + 1);
|
||||
build_items.emplace_back("", object_it->second.object_id, t, instance->printable);
|
||||
|
||||
build_items.emplace_back("", object_it->second.object_id, t, instance->printable, instance->auto_drop);
|
||||
count++;
|
||||
}
|
||||
|
||||
@@ -7220,7 +7252,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
stream << "\" " << PPATH_ATTR << "=\"" << xml_escape(item.path);
|
||||
stream << "\" " << TRANSFORM_ATTR << "=\"";
|
||||
add_transformation(stream, item.transform);
|
||||
stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable << "\"/>\n";
|
||||
stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable;
|
||||
stream << "\" " << AUTO_DROP_ATTR << "=\"" << item.auto_drop << "\"/>\n";
|
||||
}
|
||||
|
||||
stream << " </" << BUILD_TAG << ">\n";
|
||||
@@ -7535,7 +7568,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
{
|
||||
const std::string& temp_path = model.get_backup_path();
|
||||
std::string temp_file = temp_path + std::string("/") + "_temp_1.config";
|
||||
config.save_to_json(temp_file, std::string("project_settings"), std::string("project"), std::string(SoftFever_VERSION));
|
||||
config.save_to_json(temp_file, std::string("project_settings"), std::string("project"), std::string(SLIC3R_VERSION));
|
||||
return _add_file_to_archive(archive, BBS_PROJECT_CONFIG_FILE, temp_file);
|
||||
}
|
||||
|
||||
@@ -7930,7 +7963,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
// save slice header for debug
|
||||
stream << " <" << SLICE_HEADER_TAG << ">\n";
|
||||
stream << " <" << SLICE_HEADER_ITEM_TAG << " " << KEY_ATTR << "=\"" << "X-BBL-Client-Type" << "\" " << VALUE_ATTR << "=\"" << "slicer" << "\"/>\n";
|
||||
stream << " <" << SLICE_HEADER_ITEM_TAG << " " << KEY_ATTR << "=\"" << "X-BBL-Client-Version" << "\" " << VALUE_ATTR << "=\"" << convert_to_full_version(SoftFever_VERSION) << "\"/>\n";
|
||||
stream << " <" << SLICE_HEADER_ITEM_TAG << " " << KEY_ATTR << "=\"" << "X-BBL-Client-Version" << "\" " << VALUE_ATTR << "=\"" << convert_to_full_version(SLIC3R_VERSION) << "\"/>\n";
|
||||
stream << " <" << SLICE_HEADER_ITEM_TAG << " " << KEY_ATTR << "=\"" << "OrcaSlicer-Version" << "\" " << VALUE_ATTR << "=\"" << SoftFever_VERSION << "\"/>\n";
|
||||
stream << " </" << SLICE_HEADER_TAG << ">\n";
|
||||
|
||||
for (unsigned int i = 0; i < (unsigned int)plate_data_list.size(); ++i)
|
||||
@@ -8626,7 +8660,7 @@ private:
|
||||
|
||||
//BBS: add plate data list related logic
|
||||
bool load_bbs_3mf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, PlateDataPtrs* plate_data_list, std::vector<Preset*>* project_presets,
|
||||
bool* is_bbl_3mf, Semver* file_version, Import3mfProgressFn proFn, LoadStrategy strategy, BBLProject *project, int plate_id)
|
||||
bool* is_bbl_3mf, bool* is_orca_3mf, Semver* file_version, Import3mfProgressFn proFn, LoadStrategy strategy, BBLProject *project, int plate_id)
|
||||
{
|
||||
if (path == nullptr || config == nullptr || model == nullptr)
|
||||
return false;
|
||||
@@ -8634,7 +8668,7 @@ bool load_bbs_3mf(const char* path, DynamicPrintConfig* config, ConfigSubstituti
|
||||
// All import should use "C" locales for number formatting.
|
||||
CNumericLocalesSetter locales_setter;
|
||||
_BBS_3MF_Importer importer;
|
||||
bool res = importer.load_model_from_file(path, *model, *plate_data_list, *project_presets, *config, *config_substitutions, strategy, is_bbl_3mf, *file_version, proFn, project, plate_id);
|
||||
bool res = importer.load_model_from_file(path, *model, *plate_data_list, *project_presets, *config, *config_substitutions, strategy, is_bbl_3mf, is_orca_3mf, *file_version, proFn, project, plate_id);
|
||||
importer.log_errors();
|
||||
//BBS: remove legacy project logic currently
|
||||
//handle_legacy_project_loaded(importer.version(), *config);
|
||||
|
||||
@@ -248,7 +248,7 @@ struct StoreParams
|
||||
// add restore logic
|
||||
// Load the content of a 3mf file into the given model and preset bundle.
|
||||
extern bool load_bbs_3mf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, PlateDataPtrs* plate_data_list, std::vector<Preset*>* project_presets,
|
||||
bool* is_bbl_3mf, Semver* file_version, Import3mfProgressFn proFn = nullptr, LoadStrategy strategy = LoadStrategy::Default, BBLProject *project = nullptr, int plate_id = 0);
|
||||
bool* is_bbl_3mf, bool* is_orca_3mf, Semver* file_version, Import3mfProgressFn proFn = nullptr, LoadStrategy strategy = LoadStrategy::Default, BBLProject *project = nullptr, int plate_id = 0);
|
||||
|
||||
extern std::string bbs_3mf_get_thumbnail(const char * path);
|
||||
|
||||
|
||||
+113
-43
@@ -1949,6 +1949,7 @@ namespace DoExport {
|
||||
//if (ret.size() < MAX_TAGS_COUNT) check(_(L("Color Change G-code")), config.color_change_gcode.value);
|
||||
//Orca
|
||||
if (ret.size() < MAX_TAGS_COUNT) check(_(L("Change extrusion role G-code")), config.change_extrusion_role_gcode.value);
|
||||
if (ret.size() < MAX_TAGS_COUNT) check(_(L("Process change extrusion role G-code")), config.process_change_extrusion_role_gcode.value);
|
||||
if (ret.size() < MAX_TAGS_COUNT) check(_(L("Pause G-code")), config.machine_pause_gcode.value);
|
||||
if (ret.size() < MAX_TAGS_COUNT) check(_(L("Template Custom G-code")), config.template_custom_gcode.value);
|
||||
if (ret.size() < MAX_TAGS_COUNT) {
|
||||
@@ -1965,6 +1966,13 @@ namespace DoExport {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ret.size() < MAX_TAGS_COUNT) {
|
||||
for (const std::string& value : config.filament_change_extrusion_role_gcode.values) {
|
||||
check(_(L("Filament change extrusion role G-code")), value);
|
||||
if (ret.size() == MAX_TAGS_COUNT)
|
||||
break;
|
||||
}
|
||||
}
|
||||
//BBS: no custom_gcode_per_print_z, don't need to check
|
||||
//if (ret.size() < MAX_TAGS_COUNT) {
|
||||
// const CustomGCode::Info& custom_gcode_per_print_z = print.model().custom_gcode_per_print_z;
|
||||
@@ -2479,7 +2487,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
} else
|
||||
m_enable_extrusion_role_markers = false;
|
||||
|
||||
if (!print.config().small_area_infill_flow_compensation_model.empty())
|
||||
if (m_config.small_area_infill_flow_compensation.value && !m_config.small_area_infill_flow_compensation_model.empty())
|
||||
m_small_area_infill_flow_compensator = make_unique<SmallAreaInfillFlowCompensator>(print.config());
|
||||
|
||||
// Process file_start_gcode - written at the very top of the file, before any header
|
||||
@@ -3102,15 +3110,16 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
this->_print_first_layer_extruder_temperatures(file, print, machine_start_gcode, initial_extruder_id, true);
|
||||
}
|
||||
// Orca: when activate_air_filtration is set on any extruder, find and set the highest during_print_exhaust_fan_speed
|
||||
bool activate_air_filtration = false;
|
||||
bool activate_air_filtration_during_print = false;
|
||||
int during_print_exhaust_fan_speed = 0;
|
||||
for (const auto &extruder : m_writer.extruders()) {
|
||||
activate_air_filtration |= m_config.activate_air_filtration.get_at(extruder.id());
|
||||
if (m_config.activate_air_filtration.get_at(extruder.id()))
|
||||
if (m_config.activate_air_filtration.get_at(extruder.id()) && m_config.activate_air_filtration_during_print.get_at(extruder.id())) {
|
||||
activate_air_filtration_during_print = true;
|
||||
during_print_exhaust_fan_speed = std::max(during_print_exhaust_fan_speed,
|
||||
m_config.during_print_exhaust_fan_speed.get_at(extruder.id()));
|
||||
}
|
||||
}
|
||||
if (activate_air_filtration)
|
||||
if (activate_air_filtration_during_print)
|
||||
file.write(m_writer.set_exhaust_fan(during_print_exhaust_fan_speed, true));
|
||||
|
||||
print.throw_if_canceled();
|
||||
@@ -3410,13 +3419,16 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
if (activate_chamber_temp_control && max_chamber_temp > 0)
|
||||
file.write(m_writer.set_chamber_temperature(0, false)); //close chamber_temperature
|
||||
|
||||
if (activate_air_filtration) {
|
||||
int complete_print_exhaust_fan_speed = 0;
|
||||
for (const auto& extruder : m_writer.extruders())
|
||||
if (m_config.activate_air_filtration.get_at(extruder.id()))
|
||||
complete_print_exhaust_fan_speed = std::max(complete_print_exhaust_fan_speed, m_config.complete_print_exhaust_fan_speed.get_at(extruder.id()));
|
||||
file.write(m_writer.set_exhaust_fan(complete_print_exhaust_fan_speed, true));
|
||||
bool activate_air_filtration_on_completion = false;
|
||||
int complete_print_exhaust_fan_speed = 0;
|
||||
for (const auto& extruder : m_writer.extruders()) {
|
||||
if (m_config.activate_air_filtration.get_at(extruder.id()) && m_config.activate_air_filtration_on_completion.get_at(extruder.id())) {
|
||||
activate_air_filtration_on_completion = true;
|
||||
complete_print_exhaust_fan_speed = std::max(complete_print_exhaust_fan_speed, m_config.complete_print_exhaust_fan_speed.get_at(extruder.id()));
|
||||
}
|
||||
}
|
||||
if (activate_air_filtration_on_completion)
|
||||
file.write(m_writer.set_exhaust_fan(complete_print_exhaust_fan_speed, true));
|
||||
// adds tags for time estimators
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Last_Line_M73_Placeholder).c_str());
|
||||
file.write_format("; EXECUTABLE_BLOCK_END\n\n");
|
||||
@@ -4481,8 +4493,7 @@ LayerResult GCode::process_layer(
|
||||
break;
|
||||
}
|
||||
case CalibMode::Calib_Temp_Tower: {
|
||||
auto offset = static_cast<unsigned int>(print_z / 10.001) * 5;
|
||||
gcode += writer().set_temperature(print.calib_params().start - offset);
|
||||
gcode += writer().set_temperature(this->interpolate_value_across_layers(static_cast<float>(print.calib_params().start), static_cast<float>(print.calib_params().end), 5.0f));
|
||||
break;
|
||||
}
|
||||
case CalibMode::Calib_VFA_Tower: {
|
||||
@@ -5422,6 +5433,7 @@ void GCode::apply_print_config(const PrintConfig &print_config)
|
||||
&m_config.time_lapse_gcode,
|
||||
&m_config.change_filament_gcode,
|
||||
&m_config.change_extrusion_role_gcode,
|
||||
&m_config.process_change_extrusion_role_gcode,
|
||||
&m_config.printing_by_object_gcode,
|
||||
&m_config.machine_pause_gcode,
|
||||
&m_config.template_custom_gcode,
|
||||
@@ -5431,7 +5443,8 @@ void GCode::apply_print_config(const PrintConfig &print_config)
|
||||
}
|
||||
for (auto opt : std::initializer_list<ConfigOptionStrings*>{
|
||||
&m_config.filament_start_gcode,
|
||||
&m_config.filament_end_gcode
|
||||
&m_config.filament_end_gcode,
|
||||
&m_config.filament_change_extrusion_role_gcode
|
||||
}) {
|
||||
if (opt->empty())
|
||||
for (int i = 0; i < opt->size(); ++i)
|
||||
@@ -6220,9 +6233,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
|
||||
m_need_change_layer_lift_z = false;
|
||||
// Orca: ensure Z matches planned layer height
|
||||
if (_last_pos_undefined && !slope_need_z_travel) {
|
||||
gcode += this->writer().travel_to_z(m_nominal_z, "ensure Z matches planned layer height", true);
|
||||
if (!slope_need_z_travel && (_last_pos_undefined || m_need_change_layer_lift_z)) {
|
||||
const std::string z_sync_comment = _last_pos_undefined ?
|
||||
"ensure Z matches planned layer height" : ""; // no comment for normal layer-Z lift
|
||||
gcode += this->writer().travel_to_z(m_nominal_z, z_sync_comment, true);
|
||||
}
|
||||
m_need_change_layer_lift_z = false;
|
||||
}
|
||||
|
||||
if (path.z_contoured && !path.polyline.lines().empty()) {
|
||||
@@ -6315,6 +6331,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
_mm3_per_mm *= m_config.bottom_solid_infill_flow_ratio;
|
||||
} else if (path.role() == erInternalBridgeInfill) {
|
||||
_mm3_per_mm *= m_config.internal_bridge_flow;
|
||||
} else if (path.role() == erBrim) {
|
||||
_mm3_per_mm *= m_config.brim_flow_ratio;
|
||||
} else if (sloped) {
|
||||
_mm3_per_mm *= m_config.scarf_joint_flow_ratio;
|
||||
}
|
||||
@@ -6397,14 +6415,17 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
|
||||
if (speed == 0)
|
||||
speed = filament_max_volumetric_speed / _mm3_per_mm;
|
||||
if (this->on_first_layer()) {
|
||||
|
||||
const auto _layer = layer_id();
|
||||
if (this->on_first_layer() || object_layer_over_raft()) {
|
||||
//BBS: for solid infill of first layer, speed can be higher as long as
|
||||
//wall lines have be attached
|
||||
if (path.role() != erBottomSurface)
|
||||
speed = m_config.get_abs_value("initial_layer_speed");
|
||||
}
|
||||
else if(m_config.slow_down_layers > 1){
|
||||
const auto _layer = layer_id();
|
||||
if (path.role() != erBottomSurface) {
|
||||
speed = is_perimeter(path.role()) ? m_config.get_abs_value("initial_layer_speed") :
|
||||
m_config.get_abs_value("initial_layer_infill_speed");
|
||||
}
|
||||
} else if (m_config.slow_down_layers > 1 && !m_config.raft_layers > 0) {
|
||||
|
||||
if (_layer > 0 && _layer < m_config.slow_down_layers) {
|
||||
const auto first_layer_speed =
|
||||
is_perimeter(path.role())
|
||||
@@ -6414,7 +6435,18 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
speed = std::min(
|
||||
speed,
|
||||
Slic3r::lerp(first_layer_speed, speed,
|
||||
(double)_layer / m_config.slow_down_layers));
|
||||
(double) (_layer) / m_config.slow_down_layers));
|
||||
}
|
||||
}
|
||||
} else if (m_config.slow_down_layers > 1 && m_config.raft_layers > 0 ) {
|
||||
|
||||
if (_layer > m_config.raft_layers && (_layer - m_config.raft_layers) < m_config.slow_down_layers) {
|
||||
const auto first_layer_speed
|
||||
= is_perimeter(path.role()) ? m_config.get_abs_value("initial_layer_speed") :
|
||||
m_config.get_abs_value("initial_layer_infill_speed");
|
||||
if (first_layer_speed < speed) {
|
||||
speed = std::min(speed, Slic3r::lerp(first_layer_speed, speed,
|
||||
(double) (_layer - m_config.raft_layers) / m_config.slow_down_layers));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6477,7 +6509,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
bool variable_speed = false;
|
||||
std::vector<ProcessedPoint> new_points {};
|
||||
|
||||
if (m_config.enable_overhang_speed && !this->on_first_layer() &&
|
||||
if (m_config.enable_overhang_speed && !this->on_first_layer() && !object_layer_over_raft() &&
|
||||
(is_bridge(path.role()) || is_perimeter(path.role()))) {
|
||||
bool is_external = is_external_perimeter(path.role());
|
||||
double ref_speed = is_external ? m_config.get_abs_value("outer_wall_speed") : m_config.get_abs_value("inner_wall_speed");
|
||||
@@ -6565,15 +6597,29 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
// Orca: End of dynamic PA trigger flag segment
|
||||
|
||||
//Orca: process custom gcode for extrusion role change
|
||||
if (path.role() != m_last_extrusion_role && !m_config.change_extrusion_role_gcode.value.empty()) {
|
||||
if (path.role() != m_last_extrusion_role) {
|
||||
const auto current_filament_id = m_writer.filament()->id();
|
||||
const std::string& machine_role_change_gcode = m_config.change_extrusion_role_gcode.value;
|
||||
const std::string& filament_role_change_gcode = m_config.filament_change_extrusion_role_gcode.get_at(current_filament_id);
|
||||
const std::string& process_role_change_gcode = m_config.process_change_extrusion_role_gcode.value;
|
||||
|
||||
if (!machine_role_change_gcode.empty() || !filament_role_change_gcode.empty() || !process_role_change_gcode.empty()) {
|
||||
DynamicConfig config;
|
||||
config.set_key_value("extrusion_role", new ConfigOptionString(extrusion_role_to_string_for_parser(path.role())));
|
||||
config.set_key_value("last_extrusion_role", new ConfigOptionString(extrusion_role_to_string_for_parser(m_last_extrusion_role)));
|
||||
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1));
|
||||
config.set_key_value("layer_z", new ConfigOptionFloat(m_layer == nullptr ? m_last_height : m_layer->print_z));
|
||||
gcode += this->placeholder_parser_process("change_extrusion_role_gcode",
|
||||
m_config.change_extrusion_role_gcode.value, m_writer.filament()->id(), &config)
|
||||
+ "\n";
|
||||
|
||||
const auto append_role_gcode = [this, current_filament_id, &config, &gcode](const std::string& key, const std::string& templ) {
|
||||
if (templ.empty())
|
||||
return;
|
||||
gcode += this->placeholder_parser_process(key, templ, current_filament_id, &config) + "\n";
|
||||
};
|
||||
|
||||
append_role_gcode("change_extrusion_role_gcode", machine_role_change_gcode);
|
||||
append_role_gcode("filament_change_extrusion_role_gcode", filament_role_change_gcode);
|
||||
append_role_gcode("process_change_extrusion_role_gcode", process_role_change_gcode);
|
||||
}
|
||||
}
|
||||
|
||||
// extrude arc or line
|
||||
@@ -7094,14 +7140,29 @@ std::string GCode::extrusion_role_to_string_for_parser(const ExtrusionRole & rol
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the interpolated value for the current layer between start_value and end_value
|
||||
float GCode::interpolate_value_across_layers(float start_value, float end_value) const {
|
||||
if (m_layer_index == 1) {
|
||||
// Calculate the interpolated value for the current layer between start_value and end_value.
|
||||
// Step will create equal layers steps from first to last value.
|
||||
// Step = 0 means gradual interpolation finishing at last value.
|
||||
float GCode::interpolate_value_across_layers(float start_value, float end_value, float step) const
|
||||
{
|
||||
if (m_layer_index <= 1) {
|
||||
return start_value;
|
||||
} else {
|
||||
float ratio = (m_layer_index - 2.0f) / (m_layer_count - 3.0f);
|
||||
ratio = std::max(0.0f, std::min(1.0f, ratio)); // clamp
|
||||
return start_value + ratio * (end_value - start_value);
|
||||
}
|
||||
else {
|
||||
bool use_steps = step > 0.f;
|
||||
if (use_steps) {
|
||||
if (start_value > end_value) {
|
||||
start_value += step;
|
||||
} else {
|
||||
end_value += step;
|
||||
}
|
||||
}
|
||||
float ratio = m_layer_index / (m_layer_count - 1.f);
|
||||
float value = start_value + ratio * (end_value - start_value);
|
||||
if (use_steps) {
|
||||
value = trunc(value / step) * step;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7158,16 +7219,25 @@ std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string
|
||||
unsigned int acceleration_to_set = 0;
|
||||
|
||||
if (this->on_first_layer()) {
|
||||
if (m_config.default_acceleration.value > 0 && m_config.initial_layer_acceleration.value > 0) {
|
||||
acceleration_to_set = (unsigned int) floor(m_config.initial_layer_acceleration.value + 0.5);
|
||||
unsigned int initial_layer_travel_acceleration = m_config.get_abs_value("initial_layer_travel_acceleration");
|
||||
double initial_layer_travel_jerk = m_config.get_abs_value("initial_layer_travel_jerk");
|
||||
|
||||
if (m_config.default_acceleration.value > 0 && initial_layer_travel_acceleration > 0) {
|
||||
acceleration_to_set = (unsigned int) floor(initial_layer_travel_acceleration + 0.5);
|
||||
}
|
||||
|
||||
if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0) {
|
||||
jerk_to_set = m_config.initial_layer_jerk.value;
|
||||
if (m_config.default_jerk.value > 0 && initial_layer_travel_jerk > 0) {
|
||||
jerk_to_set = initial_layer_travel_jerk;
|
||||
}
|
||||
} else {
|
||||
} else { // ORCA: Handle short-travel acceleration and jerk for outer perimeters (if applicable)
|
||||
const bool is_short_travel = travel.length() < scale_(EXTRUDER_CONFIG(retraction_minimum_travel));
|
||||
|
||||
if (m_config.default_acceleration.value > 0) {
|
||||
if (role == erExternalPerimeter && travel.length() < scale_(EXTRUDER_CONFIG(retraction_minimum_travel))) {
|
||||
if (role == erOverhangPerimeter && is_short_travel) {
|
||||
const double bridge_acceleration = m_config.get_abs_value("bridge_acceleration");
|
||||
|
||||
if (bridge_acceleration > 0)
|
||||
acceleration_to_set = (unsigned int) floor(bridge_acceleration + 0.5);
|
||||
} else if (role == erExternalPerimeter && is_short_travel) {
|
||||
if (m_config.outer_wall_acceleration.value > 0)
|
||||
acceleration_to_set = (unsigned int) floor(m_config.outer_wall_acceleration.value + 0.5);
|
||||
} else {
|
||||
@@ -7177,7 +7247,7 @@ std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string
|
||||
}
|
||||
|
||||
if (m_config.default_jerk.value > 0) {
|
||||
if (role == erExternalPerimeter && travel.length() < scale_(EXTRUDER_CONFIG(retraction_minimum_travel))) {
|
||||
if ((role == erExternalPerimeter || role == erOverhangPerimeter) && is_short_travel) {
|
||||
if (m_config.outer_wall_jerk.value > 0)
|
||||
jerk_to_set = m_config.outer_wall_jerk.value;
|
||||
} else {
|
||||
|
||||
@@ -239,8 +239,10 @@ public:
|
||||
bool enable_cooling_markers() const { return m_enable_cooling_markers; }
|
||||
std::string extrusion_role_to_string_for_parser(const ExtrusionRole &);
|
||||
|
||||
// Calculate the interpolated value for the current layer between start_value and end_value
|
||||
float interpolate_value_across_layers(float start_value, float end_value) const;
|
||||
// Calculate the interpolated value for the current layer between start_value and end_value.
|
||||
// Step will create equal layers steps from first to last value.
|
||||
// Step = 0 means gradual interpolation finishing at last value.
|
||||
float interpolate_value_across_layers(float start_value, float end_value, float step = 0.0f) const;
|
||||
|
||||
// For Perl bindings, to be used exclusively by unit tests.
|
||||
unsigned int layer_count() const { return m_layer_count; }
|
||||
|
||||
@@ -220,7 +220,13 @@ ConflictComputeOpt ConflictChecker::find_inter_of_lines(const LineWithIDs &lines
|
||||
ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(PrintObjectPtrs objs,
|
||||
std::optional<const FakeWipeTower *> wtdptr) // find the first intersection point of lines in different objects
|
||||
{
|
||||
if (objs.size() <= 1 && !wtdptr) { return {}; }
|
||||
if (objs.empty() && !wtdptr) { return {}; }
|
||||
|
||||
// Orca: check if we have enough items to potentially conflict (instances count)
|
||||
size_t total_instances = 0;
|
||||
for (auto obj : objs) total_instances += obj->instances().size();
|
||||
if (total_instances <= 1 && !wtdptr) return {};
|
||||
|
||||
LinesBucketQueue conflictQueue;
|
||||
|
||||
if (wtdptr.has_value()) { // wipe tower at 0 by default
|
||||
@@ -238,8 +244,19 @@ ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(PrintObjectP
|
||||
}
|
||||
for (PrintObject *obj : objs) {
|
||||
auto layers = getAllLayersExtrusionPathsFromObject(obj);
|
||||
conflictQueue.emplace_back_bucket(std::move(layers.perimeters), obj, obj->instances().front().shift);
|
||||
conflictQueue.emplace_back_bucket(std::move(layers.support), obj, obj->instances().front().shift);
|
||||
// Orca: check for collisions between all instances
|
||||
const auto& instances = obj->instances();
|
||||
for (size_t inst_idx = 0; inst_idx < instances.size(); ++inst_idx) {
|
||||
const PrintInstance& inst = instances[inst_idx];
|
||||
const bool is_last_instance = inst_idx + 1 == instances.size();
|
||||
if (is_last_instance) {
|
||||
conflictQueue.emplace_back_bucket(std::move(layers.perimeters), &inst, inst.shift);
|
||||
conflictQueue.emplace_back_bucket(std::move(layers.support), &inst, inst.shift);
|
||||
} else {
|
||||
conflictQueue.emplace_back_bucket(ExtrusionLayers(layers.perimeters), &inst, inst.shift);
|
||||
conflictQueue.emplace_back_bucket(ExtrusionLayers(layers.support), &inst, inst.shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<LineWithIDs> layersLines;
|
||||
@@ -272,13 +289,18 @@ ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(PrintObjectP
|
||||
const FakeWipeTower *wtdp = wtdptr.value();
|
||||
if (ptr1 == wtdp || ptr2 == wtdp) {
|
||||
if (ptr2 == wtdp) { std::swap(ptr1, ptr2); }
|
||||
const PrintObject *obj2 = reinterpret_cast<const PrintObject *>(ptr2);
|
||||
return std::make_optional<ConflictResult>("WipeTower", obj2->model_object()->name, conflictPrintZ, nullptr, ptr2);
|
||||
// ptr1 is now wipe tower, ptr2 is PrintInstance*
|
||||
const PrintInstance *inst2 = reinterpret_cast<const PrintInstance *>(ptr2);
|
||||
const PrintObject *obj2 = inst2->print_object;
|
||||
return std::make_optional<ConflictResult>("WipeTower", obj2->model_object()->name, conflictPrintZ, nullptr, inst2);
|
||||
}
|
||||
}
|
||||
const PrintObject *obj1 = reinterpret_cast<const PrintObject *>(ptr1);
|
||||
const PrintObject *obj2 = reinterpret_cast<const PrintObject *>(ptr2);
|
||||
return std::make_optional<ConflictResult>(obj1->model_object()->name, obj2->model_object()->name, conflictPrintZ, ptr1, ptr2);
|
||||
|
||||
const PrintInstance *inst1 = reinterpret_cast<const PrintInstance *>(ptr1);
|
||||
const PrintInstance *inst2 = reinterpret_cast<const PrintInstance *>(ptr2);
|
||||
const PrintObject *obj1 = inst1->print_object;
|
||||
const PrintObject *obj2 = inst2->print_object;
|
||||
return std::make_optional<ConflictResult>(obj1->model_object()->name, obj2->model_object()->name, conflictPrintZ, inst1, inst2);
|
||||
} else
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -33,7 +33,11 @@ const std::string& FanMover::process_gcode(const std::string& gcode, bool flush)
|
||||
|
||||
if (flush) {
|
||||
while (!m_buffer.empty()) {
|
||||
m_process_output += m_buffer.front().raw + "\n";
|
||||
BufferData &front = m_buffer.front();
|
||||
m_process_output += front.raw + "\n";
|
||||
// Orca: Keep the emitted fan state in sync when flushing buffered fan commands.
|
||||
if (front.fan_speed >= 0)
|
||||
m_front_buffer_fan_speed = front.fan_speed;
|
||||
remove_from_buffer(m_buffer.begin());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5480,6 +5480,37 @@ void GCodeProcessor::process_filament_change(int id)
|
||||
void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type, bool internal_only)
|
||||
{
|
||||
int filament_id = get_filament_id();
|
||||
const auto normal_mode = PrintEstimatedStatistics::ETimeMode::Normal;
|
||||
const size_t normal_mode_id = static_cast<size_t>(normal_mode);
|
||||
const float delta_x = std::abs(m_end_position[X] - m_start_position[X]);
|
||||
const float delta_y = std::abs(m_end_position[Y] - m_start_position[Y]);
|
||||
const float delta_z = std::abs(m_end_position[Z] - m_start_position[Z]);
|
||||
const float delta_e = std::abs(m_end_position[E] - m_start_position[E]);
|
||||
const bool has_x = delta_x > 0.0f;
|
||||
const bool has_y = delta_y > 0.0f;
|
||||
const bool has_z = delta_z > 0.0f;
|
||||
const bool has_e = delta_e > 0.0f;
|
||||
const float move_acceleration =
|
||||
(type == EMoveType::Travel) ? get_travel_acceleration(normal_mode) :
|
||||
((type == EMoveType::Retract || type == EMoveType::Unretract) ? get_retract_acceleration(normal_mode) :
|
||||
get_acceleration(normal_mode));
|
||||
const float junction_deviation = get_option_value(m_time_processor.machine_limits.machine_max_junction_deviation, normal_mode_id);
|
||||
const bool use_jd_jerk = (m_flavor == gcfMarlinFirmware && junction_deviation > 0.0f);
|
||||
const auto axis_jerk_for_preview = [this, normal_mode, use_jd_jerk, move_acceleration](Axis axis) {
|
||||
return use_jd_jerk ? get_axis_max_jerk_with_jd(normal_mode, axis, move_acceleration) : get_axis_max_jerk(normal_mode, axis);
|
||||
};
|
||||
const float jerk_x = axis_jerk_for_preview(X);
|
||||
const float jerk_y = axis_jerk_for_preview(Y);
|
||||
const float jerk_z = axis_jerk_for_preview(Z);
|
||||
const float jerk_e = axis_jerk_for_preview(E);
|
||||
const float move_jerk =
|
||||
(has_e && !has_x && !has_y && !has_z) ? jerk_e :
|
||||
(has_z && !has_x && !has_y) ? jerk_z :
|
||||
(has_x && has_y) ? std::min(jerk_x, jerk_y) :
|
||||
has_x ? jerk_x :
|
||||
has_y ? jerk_y :
|
||||
has_z ? jerk_z :
|
||||
std::min(jerk_x, jerk_y);
|
||||
m_last_line_id = (type == EMoveType::Color_change || type == EMoveType::Pause_Print || type == EMoveType::Custom_GCode) ?
|
||||
m_line_id + 1 :
|
||||
((type == EMoveType::Seam) ? m_last_line_id : m_line_id);
|
||||
@@ -5503,6 +5534,10 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type,
|
||||
m_extruder_temps[filament_id],
|
||||
// ORCA: Add Pressure Advance visualization support
|
||||
m_pressure_advance,
|
||||
// ORCA: Add Acceleration visualization support
|
||||
move_acceleration,
|
||||
// ORCA: Add Jerk visualization support
|
||||
move_jerk,
|
||||
{ 0.0f, 0.0f }, // time
|
||||
static_cast<float>(m_layer_id), //layer_duration: set later
|
||||
std::max<unsigned int>(1, m_layer_id) - 1,
|
||||
@@ -5578,7 +5613,7 @@ float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedStatistics::ETimeM
|
||||
}
|
||||
}
|
||||
|
||||
float GCodeProcessor::get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const
|
||||
float GCodeProcessor::get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeMode mode, Axis axis, float acceleration) const
|
||||
{
|
||||
if (axis != X && axis != Y && axis != Z && axis != E)
|
||||
return 0.0f;
|
||||
@@ -5589,12 +5624,22 @@ float GCodeProcessor::get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeM
|
||||
return 0.0f;
|
||||
|
||||
const float axis_max_acc = get_axis_max_acceleration(mode, axis);
|
||||
const float generic_acc = get_acceleration(mode);
|
||||
const float effective_acc = axis_max_acc > 0.0f ? axis_max_acc : generic_acc;
|
||||
float effective_acc = acceleration;
|
||||
if (effective_acc <= 0.0f)
|
||||
effective_acc = get_acceleration(mode);
|
||||
if (axis_max_acc > 0.0f)
|
||||
effective_acc = effective_acc > 0.0f ? std::min(effective_acc, axis_max_acc) : axis_max_acc;
|
||||
if (effective_acc <= 0.0f)
|
||||
return 0.0f;
|
||||
|
||||
return std::sqrt(jd * effective_acc * 2.5f);
|
||||
}
|
||||
|
||||
float GCodeProcessor::get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const
|
||||
{
|
||||
return get_axis_max_jerk_with_jd(mode, axis, get_acceleration(mode));
|
||||
}
|
||||
|
||||
float GCodeProcessor::get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const
|
||||
{
|
||||
const size_t id = static_cast<size_t>(mode);
|
||||
|
||||
@@ -186,6 +186,10 @@ class Print;
|
||||
float temperature{ 0.0f }; // Celsius degrees
|
||||
// ORCA: Add Pressure Advance visualization support
|
||||
float pressure_advance{ 0.0f };
|
||||
// ORCA: Add Acceleration visualization support
|
||||
float acceleration{ 0.0f }; // mm/s^2
|
||||
// ORCA: Add Jerk visualization support
|
||||
float jerk{ 0.0f }; // mm/s
|
||||
std::array<float, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> time{ 0.0f, 0.0f }; // s
|
||||
float layer_duration{ 0.0f }; // s
|
||||
unsigned int layer_id{ 0 };
|
||||
@@ -1074,6 +1078,7 @@ class Print;
|
||||
// per-nozzle machine limits (filament_map_2 / get_config_idx_for_filament).
|
||||
float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeMode mode, Axis axis, float acceleration) const;
|
||||
float get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
Vec3f get_xyz_max_jerk(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
|
||||
@@ -30,7 +30,21 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config)
|
||||
m_single_extruder_multi_material = print_config.single_extruder_multi_material.value;
|
||||
bool use_mach_limits = print_config.gcode_flavor.value == gcfMarlinLegacy || print_config.gcode_flavor.value == gcfMarlinFirmware ||
|
||||
print_config.gcode_flavor.value == gcfKlipper || print_config.gcode_flavor.value == gcfRepRapFirmware;
|
||||
m_max_acceleration = std::lrint(use_mach_limits ? print_config.machine_max_acceleration_extruding.values.front() : 0);
|
||||
if (use_mach_limits) {
|
||||
// For Klipper, SET_VELOCITY_LIMIT ACCEL= applies to all moves, so the effective cap
|
||||
// is the minimum of the extruding limit and the per-axis X/Y limits.
|
||||
// This ensures user-configured Motion Ability limits are honoured (#12244).
|
||||
unsigned int extruding_limit = std::lrint(print_config.machine_max_acceleration_extruding.values.front());
|
||||
if (print_config.gcode_flavor.value == gcfKlipper) {
|
||||
unsigned int x_limit = std::lrint(print_config.machine_max_acceleration_x.values.front());
|
||||
unsigned int y_limit = std::lrint(print_config.machine_max_acceleration_y.values.front());
|
||||
if (x_limit > 0) extruding_limit = std::min(extruding_limit, x_limit);
|
||||
if (y_limit > 0) extruding_limit = std::min(extruding_limit, y_limit);
|
||||
}
|
||||
m_max_acceleration = extruding_limit;
|
||||
} else {
|
||||
m_max_acceleration = 0;
|
||||
}
|
||||
m_max_travel_acceleration = static_cast<unsigned int>(
|
||||
std::round((use_mach_limits && supports_separate_travel_acceleration(print_config.gcode_flavor.value)) ?
|
||||
print_config.machine_max_acceleration_travel.values.front() :
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include <CGAL/Polygon_mesh_processing/orient_polygon_soup.h>
|
||||
#include <CGAL/Polygon_mesh_processing/repair.h>
|
||||
#include <CGAL/Polygon_mesh_processing/remesh.h>
|
||||
#include <CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h>
|
||||
#include <CGAL/Polygon_mesh_processing/repair_polygon_soup.h>
|
||||
#include <CGAL/Polygon_mesh_processing/orientation.h>
|
||||
// BBS: for segment
|
||||
#include <CGAL/mesh_segmentation.h>
|
||||
@@ -475,6 +475,92 @@ bool empty(const CGALMesh &mesh)
|
||||
return mesh.m.is_empty();
|
||||
}
|
||||
|
||||
bool repair(TriangleMesh& mesh, RepairedMeshErrors* repaired_errors, std::string* error)
|
||||
{
|
||||
using namespace CGAL;
|
||||
namespace PMP = CGAL::Polygon_mesh_processing;
|
||||
|
||||
if (mesh.empty())
|
||||
return true;
|
||||
|
||||
try {
|
||||
// 1) Convert to polygon soup
|
||||
std::vector<_EpicMesh::Point> points;
|
||||
std::vector<std::vector<std::size_t>> polygons;
|
||||
|
||||
points.reserve(mesh.its.vertices.size());
|
||||
polygons.reserve(mesh.its.indices.size());
|
||||
|
||||
for (const auto& v : mesh.its.vertices)
|
||||
points.emplace_back(v.x(), v.y(), v.z());
|
||||
|
||||
for (const auto& f : mesh.its.indices)
|
||||
polygons.push_back({size_t(f[0]), size_t(f[1]), size_t(f[2])});
|
||||
|
||||
// 2) Aggressive soup cleanup
|
||||
PMP::repair_polygon_soup(points, polygons);
|
||||
|
||||
// 3) Convert soup → mesh
|
||||
_EpicMesh cgal_mesh;
|
||||
PMP::polygon_soup_to_polygon_mesh(points, polygons, cgal_mesh);
|
||||
|
||||
// 4) Remove degenerate geometry
|
||||
PMP::remove_degenerate_faces(cgal_mesh);
|
||||
PMP::remove_isolated_vertices(cgal_mesh);
|
||||
|
||||
// 5) Fix remaining non-manifold vertices
|
||||
PMP::duplicate_non_manifold_vertices(cgal_mesh);
|
||||
|
||||
// 6) Boolean union (keeps only outer shell)
|
||||
_EpicMesh tmp;
|
||||
if (PMP::corefine_and_compute_union(cgal_mesh, cgal_mesh, tmp)) {
|
||||
cgal_mesh = std::move(tmp);
|
||||
}
|
||||
// If it fails, continue anyway with previous mesh
|
||||
|
||||
// 7) Fill holes
|
||||
if (!CGAL::is_closed(cgal_mesh)) {
|
||||
using halfedge_descriptor = boost::graph_traits<_EpicMesh>::halfedge_descriptor;
|
||||
|
||||
std::vector<halfedge_descriptor> borders;
|
||||
PMP::extract_boundary_cycles(cgal_mesh, std::back_inserter(borders));
|
||||
|
||||
for (halfedge_descriptor h : borders) {
|
||||
PMP::triangulate_and_refine_hole(cgal_mesh, h);
|
||||
}
|
||||
}
|
||||
|
||||
// 8) Final validity check
|
||||
if (!CGAL::is_closed(cgal_mesh)) {
|
||||
if (error)
|
||||
*error = "Repair failed: mesh still open after hole filling.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 9) Ensure outward orientation
|
||||
if (!PMP::does_bound_a_volume(cgal_mesh))
|
||||
PMP::orient_to_bound_a_volume(cgal_mesh);
|
||||
|
||||
// 10) Convert back
|
||||
indexed_triangle_set its = cgal_to_indexed_triangle_set(cgal_mesh);
|
||||
|
||||
RepairedMeshErrors errs{};
|
||||
errs.facets_removed = 0;
|
||||
errs.edges_fixed = 0;
|
||||
|
||||
mesh = TriangleMesh(std::move(its), errs);
|
||||
|
||||
if (repaired_errors)
|
||||
*repaired_errors = errs;
|
||||
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
if (error)
|
||||
*error = e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
CGALMeshPtr clone(const CGALMesh &m)
|
||||
{
|
||||
return CGALMeshPtr{new CGALMesh{m}};
|
||||
|
||||
@@ -70,6 +70,9 @@ TriangleMesh merge(std::vector<TriangleMesh> meshes);
|
||||
|
||||
bool does_bound_a_volume(const CGALMesh &mesh);
|
||||
bool empty(const CGALMesh &mesh);
|
||||
|
||||
// Repair a mesh using CGAL. Returns true on success. Optionally returns a summary of repairs and an error string.
|
||||
bool repair(TriangleMesh &mesh, RepairedMeshErrors *repaired_errors = nullptr, std::string *error = nullptr);
|
||||
}
|
||||
|
||||
namespace mcut {
|
||||
|
||||
+17
-7
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "Format/AMF.hpp"
|
||||
#include "Format/svg.hpp"
|
||||
#include "Format/bbs_3mf.hpp"
|
||||
#include "Format/DRC.hpp"
|
||||
// BBS
|
||||
#include "FaceDetector.hpp"
|
||||
@@ -322,7 +323,7 @@ Model Model::read_from_file(const std::string&
|
||||
// BBS: backup & restore
|
||||
//FIXME options & LoadStrategy::CheckVersion ?
|
||||
//BBS: is_xxx is used for is_bbs_3mf when load 3mf
|
||||
result = load_bbs_3mf(input_file.c_str(), config, config_substitutions, &model, plate_data, project_presets, is_xxx, file_version, proFn, options, project, plate_id);
|
||||
result = load_bbs_3mf(input_file.c_str(), config, config_substitutions, &model, plate_data, project_presets, is_xxx, nullptr, file_version, proFn, options, project, plate_id);
|
||||
#ifdef __APPLE__
|
||||
else if (boost::algorithm::iends_with(input_file, ".usd") || boost::algorithm::iends_with(input_file, ".usda") ||
|
||||
boost::algorithm::iends_with(input_file, ".usdc") || boost::algorithm::iends_with(input_file, ".usdz") ||
|
||||
@@ -381,7 +382,8 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
|
||||
Model model;
|
||||
|
||||
bool result = false;
|
||||
bool is_bbl_3mf;
|
||||
bool is_bbl_3mf = false;
|
||||
bool is_orca_3mf = false;
|
||||
if (boost::algorithm::iends_with(input_file, ".3mf")) {
|
||||
PrusaFileParser prusa_file_parser;
|
||||
if (prusa_file_parser.check_3mf_from_prusa(input_file)) {
|
||||
@@ -391,7 +393,7 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
|
||||
} else {
|
||||
// BBS: add part plate related logic
|
||||
// BBS: backup & restore
|
||||
result = load_bbs_3mf(input_file.c_str(), config, config_substitutions, &model, plate_data, project_presets, &is_bbl_3mf, file_version, proFn, options, project);
|
||||
result = load_bbs_3mf(input_file.c_str(), config, config_substitutions, &model, plate_data, project_presets, &is_bbl_3mf, &is_orca_3mf, file_version, proFn, options, project);
|
||||
}
|
||||
}
|
||||
else if (boost::algorithm::iends_with(input_file, ".zip.amf"))
|
||||
@@ -400,7 +402,10 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
|
||||
throw Slic3r::RuntimeError(_L("Unknown file format. Input file must have .3mf or .zip.amf extension."));
|
||||
|
||||
if (out_file_type != En3mfType::From_Prusa) {
|
||||
out_file_type = is_bbl_3mf ? En3mfType::From_BBS : En3mfType::From_Other;
|
||||
if (is_orca_3mf)
|
||||
out_file_type = En3mfType::From_Orca;
|
||||
else
|
||||
out_file_type = is_bbl_3mf ? En3mfType::From_BBS : En3mfType::From_Other;
|
||||
}
|
||||
|
||||
if (!result)
|
||||
@@ -1088,7 +1093,6 @@ bool Model::is_fuzzy_skin_painted() const
|
||||
return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_fuzzy_skin_painted(); });
|
||||
}
|
||||
|
||||
|
||||
static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART)
|
||||
{
|
||||
if (mesh.empty())
|
||||
@@ -1734,8 +1738,14 @@ void ModelObject::ensure_on_bed(bool allow_negative_z)
|
||||
else
|
||||
z_offset = -this->min_z();
|
||||
|
||||
if (z_offset != 0.0)
|
||||
translate_instances(z_offset * Vec3d::UnitZ());
|
||||
if (z_offset != 0.0) {
|
||||
for (size_t i = 0; i < instances.size(); ++i) {
|
||||
if (!instances[i]->auto_drop)
|
||||
continue;
|
||||
|
||||
translate_instance(i, z_offset * Vec3d::UnitZ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelObject::translate_instances(const Vec3d& vector)
|
||||
|
||||
@@ -719,6 +719,7 @@ enum class ConversionType : int {
|
||||
};
|
||||
|
||||
enum class En3mfType : int {
|
||||
From_Orca,
|
||||
From_BBS,
|
||||
From_Prusa,
|
||||
From_Other
|
||||
@@ -1251,6 +1252,7 @@ public:
|
||||
ModelInstanceEPrintVolumeState print_volume_state;
|
||||
// Whether or not this instance is printable
|
||||
bool printable;
|
||||
bool auto_drop;
|
||||
bool use_loaded_id_for_label {false};
|
||||
int arrange_order = 0; // BBS
|
||||
size_t loaded_id = 0; // BBS
|
||||
@@ -1379,7 +1381,11 @@ private:
|
||||
Polygon convex_hull; // BBS
|
||||
|
||||
// Constructor, which assigns a new unique ID.
|
||||
explicit ModelInstance(ModelObject* object) : print_volume_state(ModelInstancePVS_Inside), printable(true), object(object), m_assemble_initialized(false) { assert(this->id().valid()); }
|
||||
explicit ModelInstance(ModelObject* object)
|
||||
: print_volume_state(ModelInstancePVS_Inside), printable(true), auto_drop(true), object(object), m_assemble_initialized(false)
|
||||
{
|
||||
assert(this->id().valid());
|
||||
}
|
||||
// Constructor, which assigns a new unique ID.
|
||||
explicit ModelInstance(ModelObject *object, const ModelInstance &other) :
|
||||
m_transformation(other.m_transformation)
|
||||
@@ -1387,6 +1393,7 @@ private:
|
||||
, m_offset_to_assembly(other.m_offset_to_assembly)
|
||||
, print_volume_state(ModelInstancePVS_Inside)
|
||||
, printable(other.printable)
|
||||
, auto_drop(other.auto_drop)
|
||||
, object(object)
|
||||
, m_assemble_initialized(false) { assert(this->id().valid() && this->id() != other.id()); }
|
||||
|
||||
@@ -1400,7 +1407,7 @@ private:
|
||||
ModelInstance() : ObjectBase(-1), object(nullptr) { assert(this->id().invalid()); }
|
||||
// BBS. Add added members to archive.
|
||||
template<class Archive> void serialize(Archive& ar) {
|
||||
ar(m_transformation, print_volume_state, printable, m_assemble_transformation, m_offset_to_assembly, m_assemble_initialized);
|
||||
ar(m_transformation, print_volume_state, printable, auto_drop, m_assemble_transformation, m_offset_to_assembly, m_assemble_initialized);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1571,9 +1571,9 @@ void PerimeterGenerator::process_classic()
|
||||
} // for each loop of an island
|
||||
|
||||
// fill gaps
|
||||
if (! gaps.empty()) {
|
||||
// collapse
|
||||
double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE);
|
||||
if (! gaps.empty()) { // collapse
|
||||
// ORCA: Use the smaller width as the lower bound to avoid overestimating safe overlap
|
||||
double min = 0.2 * std::min(perimeter_width, ext_perimeter_width) * (1 - INSET_OVERLAP_TOLERANCE);
|
||||
double max = 2. * perimeter_spacing;
|
||||
ExPolygons gaps_ex = diff_ex(
|
||||
//FIXME offset2 would be enough and cheaper.
|
||||
@@ -1738,8 +1738,12 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
//compute our unsupported surface
|
||||
ExPolygons unsupported = diff_ex(last, *this->lower_slices, ApplySafetyOffset::Yes);
|
||||
if (!unsupported.empty()) {
|
||||
//remove small overhangs
|
||||
ExPolygons unsupported_filtered = offset2_ex(unsupported, double(-perimeter_spacing), double(perimeter_spacing));
|
||||
// remove small overhangs (when using chbFilled we need to be less aggressive in removing small overhangs,
|
||||
// to avoid affecting bridging detection.)
|
||||
const int outset_divisor = this->config->counterbore_hole_bridging.value == chbFilled ? 2 : 1;
|
||||
ExPolygons unsupported_filtered = offset2_ex(unsupported, double(-perimeter_spacing),
|
||||
double(perimeter_spacing) / outset_divisor);
|
||||
|
||||
if (!unsupported_filtered.empty()) {
|
||||
//to_draw.insert(to_draw.end(), last.begin(), last.end());
|
||||
//extract only the useful part of the lower layer. The safety offset is really needed here.
|
||||
@@ -1782,7 +1786,7 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
}
|
||||
}
|
||||
unsupported_filtered = intersection_ex(last,
|
||||
offset2_ex(unsupported_filtered, double(-perimeter_spacing / 2), double(bridged_infill_margin + perimeter_spacing / 2)));
|
||||
offset_ex(unsupported_filtered, 0.5 * double(bridged_infill_margin)));
|
||||
if (this->config->counterbore_hole_bridging.value == chbFilled) {
|
||||
for (ExPolygon& expol : unsupported_filtered) {
|
||||
//check if the holes won't be covered by the upper layer
|
||||
@@ -1879,6 +1883,20 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
bridges_temp = diff_ex(bridges_temp, unbridgeable);
|
||||
unsupported_filtered = offset_ex(bridges_temp, offset_to_do);
|
||||
unsupported_filtered = intersection_ex(unsupported_filtered, reference);
|
||||
|
||||
// Normalize anchor size for partial bridges:
|
||||
// derive the bridge core first, then add a fixed overlap into support.
|
||||
const coordf_t anchor_overlap = bridged_infill_margin;
|
||||
ExPolygons bridge_core = diff_ex(unsupported_filtered, support, ApplySafetyOffset::Yes);
|
||||
if (bridge_core.empty()) {
|
||||
bridge_core = unsupported_filtered;
|
||||
}
|
||||
ExPolygons anchor_overlap_area = intersection_ex(
|
||||
offset_ex(bridge_core, anchor_overlap),
|
||||
support,
|
||||
ApplySafetyOffset::Yes);
|
||||
unsupported_filtered = union_ex(bridge_core, anchor_overlap_area);
|
||||
unsupported_filtered = intersection_ex(unsupported_filtered, reference);
|
||||
// } else {
|
||||
// ExPolygons unbridgeable = intersection_ex(unsupported, diff_ex(unsupported_filtered, offset_ex(bridgeable_simplified, ext_perimeter_width / 2)));
|
||||
// unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width, ext_perimeter_width);
|
||||
|
||||
@@ -962,7 +962,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"top_surface_speed", "support_speed", "support_object_xy_distance", "support_object_first_layer_gap", "support_interface_speed",
|
||||
"bridge_speed", "internal_bridge_speed", "gap_infill_speed", "travel_speed", "travel_speed_z", "initial_layer_speed",
|
||||
"outer_wall_acceleration", "initial_layer_acceleration", "top_surface_acceleration", "default_acceleration", "skirt_type", "skirt_loops", "skirt_speed","min_skirt_length", "skirt_distance", "skirt_start_angle", "skirt_height","single_loop_draft_shield", "draft_shield",
|
||||
"brim_width", "brim_object_gap", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers",
|
||||
"brim_width", "brim_object_gap", "brim_flow_ratio", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers",
|
||||
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
|
||||
"support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_style",
|
||||
// BBS
|
||||
@@ -1189,9 +1189,9 @@ static std::vector<std::string> s_Preset_filament_options {/*"filament_colour",
|
||||
//BBS:temperature_vitrification
|
||||
"temperature_vitrification", "reduce_fan_stop_start_freq","dont_slow_down_outer_wall", "slow_down_for_layer_cooling", "fan_min_speed",
|
||||
"fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "full_fan_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed",
|
||||
"filament_start_gcode", "filament_end_gcode",
|
||||
"filament_start_gcode", "filament_end_gcode", "filament_change_extrusion_role_gcode",
|
||||
//exhaust fan control
|
||||
"activate_air_filtration","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed",
|
||||
"activate_air_filtration","activate_air_filtration_during_print","activate_air_filtration_on_completion","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed",
|
||||
// Retract overrides
|
||||
"filament_retraction_length", "filament_z_hop", "filament_z_hop_types", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_lift_enforce", "filament_retraction_speed", "filament_deretraction_speed", "filament_retract_restart_extra", "filament_retraction_minimum_travel",
|
||||
"filament_retract_when_changing_layer", "filament_wipe", "filament_retract_before_wipe",
|
||||
|
||||
@@ -2197,16 +2197,32 @@ void PresetBundle::set_num_filaments(unsigned int n, std::vector<std::string> ne
|
||||
filament_presets.resize(n);
|
||||
}
|
||||
ConfigOptionStrings* filament_color = project_config.option<ConfigOptionStrings>("filament_colour");
|
||||
ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
|
||||
ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
|
||||
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map");
|
||||
|
||||
|
||||
filament_color->resize(n);
|
||||
// Sync filament multi colour
|
||||
filament_multi_color->values.resize(n);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
filament_multi_color->values[i] = filament_color->values[i];
|
||||
}
|
||||
filament_color_type->resize(n);
|
||||
filament_map->values.resize(n, 1);
|
||||
ams_multi_color_filment.resize(n);
|
||||
|
||||
// BBS set new filament color to new_color
|
||||
if (old_filament_count < n) {
|
||||
if (!new_colors.empty()) {
|
||||
for (int i = old_filament_count; i < n; i++) {
|
||||
filament_color->values[i] = new_colors[i - old_filament_count];
|
||||
filament_multi_color->values[i] = new_colors[i - old_filament_count];
|
||||
filament_color_type->values[i] = "1"; // default color type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_multi_material_filament_presets();
|
||||
}
|
||||
void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
|
||||
|
||||
+74
-41
@@ -219,6 +219,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
"ironing_fan_speed",
|
||||
"single_extruder_multi_material_priming",
|
||||
"activate_air_filtration",
|
||||
"activate_air_filtration_during_print",
|
||||
"activate_air_filtration_on_completion",
|
||||
"during_print_exhaust_fan_speed",
|
||||
"complete_print_exhaust_fan_speed",
|
||||
"activate_chamber_temp_control",
|
||||
@@ -343,6 +345,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
|| opt_key == "travel_speed_z"
|
||||
|| opt_key == "initial_layer_speed"
|
||||
|| opt_key == "initial_layer_travel_speed"
|
||||
|| opt_key == "initial_layer_travel_acceleration"
|
||||
|| opt_key == "initial_layer_travel_jerk"
|
||||
|| opt_key == "slow_down_layers"
|
||||
|| opt_key == "idle_temperature"
|
||||
|| opt_key == "wipe_tower_cone_angle"
|
||||
@@ -610,7 +614,6 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print
|
||||
std::for_each(exclude_polys.begin(), exclude_polys.end(),
|
||||
[&print_origin](Polygon& p) { p.translate(scale_(print_origin.x()), scale_(print_origin.y())); });
|
||||
|
||||
std::map<ObjectID, Polygon> map_model_object_to_convex_hull;
|
||||
struct print_instance_info
|
||||
{
|
||||
const PrintInstance *print_instance;
|
||||
@@ -645,27 +648,13 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print
|
||||
for (const PrintObject *print_object : print.objects()) {
|
||||
assert(! print_object->model_object()->instances.empty());
|
||||
assert(! print_object->instances().empty());
|
||||
ObjectID model_object_id = print_object->model_object()->id();
|
||||
auto it_convex_hull = map_model_object_to_convex_hull.find(model_object_id);
|
||||
// Get convex hull of all printable volumes assigned to this print object.
|
||||
ModelInstance *model_instance0 = print_object->model_object()->instances.front();
|
||||
if (it_convex_hull == map_model_object_to_convex_hull.end()) {
|
||||
// Calculate the convex hull of a printable object.
|
||||
// Grow convex hull with the clearance margin.
|
||||
// FIXME: Arrangement has different parameters for offsetting (jtMiter, limit 2)
|
||||
// which causes that the warning will be showed after arrangement with the
|
||||
// appropriate object distance. Even if I set this to jtMiter the warning still shows up.
|
||||
it_convex_hull = map_model_object_to_convex_hull.emplace_hint(it_convex_hull, model_object_id,
|
||||
print_object->model_object()->convex_hull_2d(Geometry::assemble_transform(
|
||||
{ 0.0, 0.0, model_instance0->get_offset().z() }, model_instance0->get_rotation(), model_instance0->get_scaling_factor(), model_instance0->get_mirror())));
|
||||
}
|
||||
// Make a copy, so it may be rotated for instances.
|
||||
Polygon convex_hull0 = it_convex_hull->second;
|
||||
const double z_diff = Geometry::rotation_diff_z(model_instance0->get_rotation(), print_object->instances().front().model_instance->get_rotation());
|
||||
if (std::abs(z_diff) > EPSILON)
|
||||
convex_hull0.rotate(z_diff);
|
||||
|
||||
// Orca: check convex hull intersection for each instance individually to handle rotation/offset differences correctly
|
||||
// Now we check that no instance of convex_hull intersects any of the previously checked object instances.
|
||||
for (const PrintInstance &instance : print_object->instances()) {
|
||||
Polygon convex_hull0 = print_object->model_object()->convex_hull_2d(Geometry::assemble_transform(
|
||||
{ 0.0, 0.0, instance.model_instance->get_offset().z() }, instance.model_instance->get_rotation(), instance.model_instance->get_scaling_factor(), instance.model_instance->get_mirror()));
|
||||
|
||||
Polygon convex_hull_no_offset = convex_hull0, convex_hull;
|
||||
auto tmp = offset(convex_hull_no_offset, obj_distance, jtRound, scale_(0.1));
|
||||
if (!tmp.empty()) { // tmp may be empty due to clipper's bug, see STUDIO-2452
|
||||
@@ -679,7 +668,9 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print
|
||||
if (!intersection(exclude_polys, convex_hull_no_offset).empty()) {
|
||||
if (single_object_exception.string.empty()) {
|
||||
single_object_exception.string = (boost::format(L("%1% is too close to exclusion area, there may be collisions when printing.")) %instance.model_instance->get_object()->name).str();
|
||||
single_object_exception.object = instance.model_instance->get_object();
|
||||
// single_object_exception.object = instance.model_instance->get_object();
|
||||
//ORCA: Pass ModelInstance instead of ModelObject
|
||||
single_object_exception.object = instance.model_instance;
|
||||
}
|
||||
else {
|
||||
single_object_exception.string += "\n"+(boost::format(L("%1% is too close to exclusion area, there may be collisions when printing.")) %instance.model_instance->get_object()->name).str();
|
||||
@@ -696,12 +687,16 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print
|
||||
bool has_exception = false;
|
||||
if (single_object_exception.string.empty()) {
|
||||
single_object_exception.string = (boost::format(L("%1% is too close to others, and collisions may be caused.")) %instance.model_instance->get_object()->name).str();
|
||||
single_object_exception.object = instance.model_instance->get_object();
|
||||
// single_object_exception.object = instance.model_instance->get_object();
|
||||
//ORCA: Pass ModelInstance instead of ModelObject for better selection
|
||||
single_object_exception.object = instance.model_instance;
|
||||
has_exception = true;
|
||||
}
|
||||
else {
|
||||
single_object_exception.string += "\n"+(boost::format(L("%1% is too close to others, and collisions may be caused.")) %instance.model_instance->get_object()->name).str();
|
||||
single_object_exception.object = nullptr;
|
||||
// single_object_exception.object = nullptr;
|
||||
// ORCA: Keep the first object so jump works
|
||||
// has_exception = true;
|
||||
has_exception = true;
|
||||
}
|
||||
|
||||
@@ -948,33 +943,52 @@ static StringObjectException layered_print_cleareance_valid(const Print &print,
|
||||
wrapping_poly.points.emplace_back(scale_(pt.x() + print_origin.x()), scale_(pt.y() + print_origin.y()));
|
||||
}
|
||||
|
||||
std::map<const ModelVolume*, Polygon> map_model_volume_to_convex_hull;
|
||||
Polygons convex_hulls_other;
|
||||
// Orca: check convex hull intersection for each instance individually
|
||||
for (auto& inst : print_instances_ordered) {
|
||||
Polygons current_instance_hulls;
|
||||
for (const ModelVolume *v : inst->print_object->model_object()->volumes) {
|
||||
if (!v->is_model_part()) continue;
|
||||
auto it_convex_hull = map_model_volume_to_convex_hull.find(v);
|
||||
if (it_convex_hull == map_model_volume_to_convex_hull.end()) {
|
||||
auto volume_hull = v->get_convex_hull_2d(Geometry::assemble_transform(Vec3d::Zero(), inst->model_instance->get_rotation(),
|
||||
inst->model_instance->get_scaling_factor(), inst->model_instance->get_mirror()));
|
||||
volume_hull.translate(inst->shift - inst->print_object->center_offset());
|
||||
|
||||
auto volume_hull = v->get_convex_hull_2d(Geometry::assemble_transform(Vec3d::Zero(), inst->model_instance->get_rotation(),
|
||||
inst->model_instance->get_scaling_factor(), inst->model_instance->get_mirror()));
|
||||
volume_hull.translate(inst->shift - inst->print_object->center_offset());
|
||||
|
||||
it_convex_hull = map_model_volume_to_convex_hull.emplace_hint(it_convex_hull, v, volume_hull);
|
||||
}
|
||||
Polygon &convex_hull = it_convex_hull->second;
|
||||
Polygons convex_hulls_temp;
|
||||
convex_hulls_temp.push_back(convex_hull);
|
||||
if (!intersection(exclude_polys, convex_hull).empty()) {
|
||||
if (!intersection(exclude_polys, volume_hull).empty()) {
|
||||
// return {inst->model_instance->get_object()->name + L(" is too close to exclusion area, there may be collisions when printing.") + "\n",
|
||||
// inst->model_instance->get_object()};
|
||||
//ORCA: Pass ModelInstance instead of ModelObject
|
||||
return {inst->model_instance->get_object()->name + L(" is too close to exclusion area, there may be collisions when printing.") + "\n",
|
||||
inst->model_instance->get_object()};
|
||||
inst->model_instance};
|
||||
}
|
||||
|
||||
if (print_config.enable_wrapping_detection.value && !intersection(wrapping_poly, convex_hull).empty()) {
|
||||
if (print_config.enable_wrapping_detection.value && !intersection(wrapping_poly, volume_hull).empty()) {
|
||||
// return {inst->model_instance->get_object()->name + L(" is too close to clumping detection area, there may be collisions when printing.") + "\n",
|
||||
// inst->model_instance->get_object()};
|
||||
//ORCA: Pass ModelInstance instead of ModelObject
|
||||
return {inst->model_instance->get_object()->name + L(" is too close to clumping detection area, there may be collisions when printing.") + "\n",
|
||||
inst->model_instance->get_object()};
|
||||
inst->model_instance};
|
||||
}
|
||||
convex_hulls_other.emplace_back(convex_hull);
|
||||
current_instance_hulls.emplace_back(volume_hull);
|
||||
}
|
||||
|
||||
if (!intersection(convex_hulls_other, current_instance_hulls).empty()) {
|
||||
if (warning) {
|
||||
if (warning->string.empty()) {
|
||||
warning->string = (boost::format(L("%1% is too close to others, and collisions may be caused.")) % inst->model_instance->get_object()->name).str();
|
||||
// warning->object = inst->model_instance->get_object();
|
||||
//ORCA: Pass ModelInstance instead of ModelObject for better selection
|
||||
warning->object = inst->model_instance;
|
||||
} else {
|
||||
warning->string += "\n" + (boost::format(L("%1% is too close to others, and collisions may be caused.")) % inst->model_instance->get_object()->name).str();
|
||||
// ORCA: Keep the first object so jump works
|
||||
if (!warning->object) warning->object = inst->model_instance;
|
||||
}
|
||||
warning->is_warning = true;
|
||||
warning->type = STRING_EXCEPT_OBJECT_COLLISION_IN_LAYER_PRINT;
|
||||
}
|
||||
}
|
||||
append(convex_hulls_other, current_instance_hulls);
|
||||
}
|
||||
|
||||
//BBS: add the wipe tower check logic
|
||||
@@ -4612,7 +4626,7 @@ int Print::export_cached_data(const std::string& directory, bool with_space)
|
||||
/*boost::nowide::ofstream c;
|
||||
c.open(file_name, std::ios::out | std::ios::trunc);
|
||||
if (with_space)
|
||||
c << std::setw(4) << root_json << std::endl;
|
||||
c << root_json.dump(1, '\t') << std::endl;
|
||||
else
|
||||
c << root_json.dump(0) << std::endl;
|
||||
c.close();*/
|
||||
@@ -4634,7 +4648,7 @@ int Print::export_cached_data(const std::string& directory, bool with_space)
|
||||
boost::nowide::ofstream c;
|
||||
c.open(filename_vector[object_index], std::ios::out | std::ios::trunc);
|
||||
if (with_space)
|
||||
c << std::setw(4) << json_vector[object_index] << std::endl;
|
||||
c << json_vector[object_index].dump(1, '\t') << std::endl;
|
||||
else
|
||||
c << json_vector[object_index].dump(0) << std::endl;
|
||||
c.close();
|
||||
@@ -4908,6 +4922,25 @@ ExtrusionLayers FakeWipeTower::getTrueExtrusionLayersFromWipeTower() const
|
||||
{
|
||||
ExtrusionLayers wtels;
|
||||
wtels.type = ExtrusionLayersType::WIPE_TOWER;
|
||||
|
||||
//ORCA: Fallback for WipeTower2 if outer_wall is empty
|
||||
if (outer_wall.empty()) {
|
||||
auto fake_paths = getFakeExtrusionPathsFromWipeTower2();
|
||||
float current_z = 0.f;
|
||||
for (auto& layer_paths : fake_paths) {
|
||||
if (layer_paths.empty()) continue;
|
||||
ExtrusionLayer el;
|
||||
float lh = layer_paths.front().height;
|
||||
el.height = lh;
|
||||
el.bottom_z = current_z;
|
||||
el.layer = nullptr;
|
||||
el.paths = std::move(layer_paths);
|
||||
wtels.push_back(std::move(el));
|
||||
current_z += lh;
|
||||
}
|
||||
return wtels;
|
||||
}
|
||||
|
||||
std::vector<float> layer_heights;
|
||||
layer_heights.reserve(outer_wall.size());
|
||||
auto pre = outer_wall.begin();
|
||||
|
||||
@@ -1163,7 +1163,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
|
||||
m_ori_full_print_config = new_full_config;
|
||||
new_full_config.update_values_to_printer_extruders_for_multiple_filaments(new_full_config, filament_options_with_variant, "filament_self_index", "filament_extruder_variant");
|
||||
std::vector<int> filament_maps = new_full_config.option<ConfigOptionInts>("filament_map")->values;
|
||||
auto opt_filament_map = new_full_config.option<ConfigOptionInts>("filament_map");
|
||||
std::vector<int> filament_maps = opt_filament_map ? opt_filament_map->values : std::vector<int>();
|
||||
|
||||
// Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles.
|
||||
DynamicPrintConfig filament_overrides;
|
||||
|
||||
+307
-168
File diff suppressed because it is too large
Load Diff
@@ -888,6 +888,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
PrintObjectConfig,
|
||||
|
||||
((ConfigOptionFloat, brim_object_gap))
|
||||
((ConfigOptionFloat, brim_flow_ratio))
|
||||
((ConfigOptionBool, brim_use_efc_outline))
|
||||
((ConfigOptionEnum<BrimType>, brim_type))
|
||||
((ConfigOptionFloat, brim_width))
|
||||
@@ -897,6 +898,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionBool, bridge_no_support))
|
||||
((ConfigOptionFloat, elefant_foot_compensation))
|
||||
((ConfigOptionInt, elefant_foot_compensation_layers))
|
||||
((ConfigOptionPercent, elefant_foot_layers_density))
|
||||
((ConfigOptionFloat, max_bridge_length))
|
||||
((ConfigOptionFloatOrPercent, line_width))
|
||||
// Force the generation of solid shells between adjacent materials/volumes.
|
||||
@@ -995,6 +997,8 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionPercent, min_bead_width))
|
||||
|
||||
// Orca
|
||||
((ConfigOptionFloat, wall_maximum_resolution))
|
||||
((ConfigOptionFloat, wall_maximum_deviation))
|
||||
((ConfigOptionFloat, make_overhang_printable_angle))
|
||||
((ConfigOptionFloat, make_overhang_printable_hole_size))
|
||||
((ConfigOptionFloat, tree_support_branch_distance_organic))
|
||||
@@ -1269,6 +1273,8 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionBool, wipe_tower_no_sparse_layers))
|
||||
((ConfigOptionString, change_filament_gcode))
|
||||
((ConfigOptionString, change_extrusion_role_gcode))
|
||||
((ConfigOptionString, process_change_extrusion_role_gcode))
|
||||
((ConfigOptionStrings, filament_change_extrusion_role_gcode))
|
||||
((ConfigOptionFloat, travel_speed))
|
||||
((ConfigOptionFloat, travel_speed_z))
|
||||
((ConfigOptionBool, silent_mode))
|
||||
@@ -1295,6 +1301,8 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionBool, accel_to_decel_enable))
|
||||
((ConfigOptionPercent, accel_to_decel_factor))
|
||||
((ConfigOptionFloatOrPercent, initial_layer_travel_speed))
|
||||
((ConfigOptionFloatOrPercent, initial_layer_travel_acceleration))
|
||||
((ConfigOptionFloatOrPercent, initial_layer_travel_jerk))
|
||||
((ConfigOptionBool, bbl_calib_mark_logo))
|
||||
((ConfigOptionBool, disable_m73))
|
||||
|
||||
@@ -1389,6 +1397,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionBools, dont_slow_down_outer_wall))
|
||||
((ConfigOptionFloats, fan_cooling_layer_time))
|
||||
((ConfigOptionBools, activate_air_filtration))
|
||||
((ConfigOptionBools, activate_air_filtration_during_print))
|
||||
((ConfigOptionBools, activate_air_filtration_on_completion))
|
||||
((ConfigOptionInts, during_print_exhaust_fan_speed))
|
||||
((ConfigOptionInts, complete_print_exhaust_fan_speed))
|
||||
((ConfigOptionFloatOrPercent, initial_layer_line_width))
|
||||
|
||||
@@ -1162,6 +1162,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
} else if (
|
||||
opt_key == "elefant_foot_compensation"
|
||||
|| opt_key == "elefant_foot_compensation_layers"
|
||||
|| opt_key == "elefant_foot_layers_density"
|
||||
|| opt_key == "support_top_z_distance"
|
||||
|| opt_key == "support_bottom_z_distance"
|
||||
|| opt_key == "xy_hole_compensation"
|
||||
@@ -1359,6 +1360,8 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "wall_transition_filter_deviation"
|
||||
|| opt_key == "wall_transition_angle"
|
||||
|| opt_key == "wall_distribution_count"
|
||||
|| opt_key == "wall_maximum_resolution"
|
||||
|| opt_key == "wall_maximum_deviation"
|
||||
|| opt_key == "min_feature_size"
|
||||
|| opt_key == "min_length_factor"
|
||||
|| opt_key == "min_bead_width") {
|
||||
@@ -1370,7 +1373,6 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "scarf_angle_threshold"
|
||||
|| opt_key == "scarf_overhang_threshold"
|
||||
|| opt_key == "scarf_joint_speed"
|
||||
|| opt_key == "scarf_joint_flow_ratio"
|
||||
|| opt_key == "seam_slope_start_height"
|
||||
|| opt_key == "seam_slope_entire_loop"
|
||||
|| opt_key == "seam_slope_min_length"
|
||||
@@ -1394,7 +1396,24 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "bed_mesh_min"
|
||||
|| opt_key == "bed_mesh_max"
|
||||
|| opt_key == "adaptive_bed_mesh_margin"
|
||||
|| opt_key == "bed_mesh_probe_distance") {
|
||||
|| opt_key == "bed_mesh_probe_distance"
|
||||
|| opt_key == "print_flow_ratio"
|
||||
|| opt_key == "first_layer_flow_ratio"
|
||||
|| opt_key == "top_solid_infill_flow_ratio"
|
||||
|| opt_key == "bottom_solid_infill_flow_ratio"
|
||||
|| opt_key == "outer_wall_flow_ratio"
|
||||
|| opt_key == "inner_wall_flow_ratio"
|
||||
|| opt_key == "overhang_flow_ratio"
|
||||
|| opt_key == "sparse_infill_flow_ratio"
|
||||
|| opt_key == "internal_solid_infill_flow_ratio"
|
||||
|| opt_key == "gap_fill_flow_ratio"
|
||||
|| opt_key == "support_flow_ratio"
|
||||
|| opt_key == "support_interface_flow_ratio"
|
||||
|| opt_key == "brim_flow_ratio"
|
||||
|| opt_key == "filament_flow_ratio"
|
||||
|| opt_key == "scarf_joint_flow_ratio"
|
||||
|| opt_key == "spiral_starting_flow_ratio"
|
||||
|| opt_key == "spiral_finishing_flow_ratio") {
|
||||
invalidated |= m_print->invalidate_step(psGCodeExport);
|
||||
} else if (
|
||||
opt_key == "flush_into_infill"
|
||||
|
||||
@@ -2884,8 +2884,9 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp
|
||||
intermediate_layers.push_back(&layer_new);
|
||||
}
|
||||
} else {
|
||||
// Insert intermediate layers.
|
||||
size_t n_layers_extra = size_t(ceil(dist / m_slicing_params.max_suport_layer_height));
|
||||
// ORCA: Bias by EPSILON so a gap effectively equal to
|
||||
// max_suport_layer_height is not split by floating-point noise.
|
||||
size_t n_layers_extra = size_t(ceil((dist - EPSILON) / m_slicing_params.max_suport_layer_height));
|
||||
assert(n_layers_extra > 0);
|
||||
coordf_t step = dist / coordf_t(n_layers_extra);
|
||||
if (extr1 != nullptr && extr1->layer_type == SupporLayerType::TopContact &&
|
||||
@@ -2900,7 +2901,9 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp
|
||||
layer_new.height = extr1->height;
|
||||
intermediate_layers.push_back(&layer_new);
|
||||
dist = extr2z - extr1z;
|
||||
n_layers_extra = size_t(ceil(dist / m_slicing_params.max_suport_layer_height));
|
||||
// ORCA: Recalculate with the same EPSILON bias after re-anchoring at the top
|
||||
// contact layer so near-equal gaps do not gain an extra split here either.
|
||||
n_layers_extra = size_t(ceil((dist - EPSILON) / m_slicing_params.max_suport_layer_height));
|
||||
if (n_layers_extra == 0)
|
||||
continue;
|
||||
// Continue printing the other layers up to extr2z.
|
||||
|
||||
@@ -1157,12 +1157,17 @@ void TreeSupport::create_tree_support_layers()
|
||||
|
||||
// Layers between the raft contacts and bottom of the object.
|
||||
double dist_to_go = m_slicing_params.object_print_z_min - raft_print_z;
|
||||
auto nsteps = int(ceil(dist_to_go / m_slicing_params.max_suport_layer_height));
|
||||
double height = dist_to_go / nsteps;
|
||||
for (int i = 0; i < nsteps; ++i) {
|
||||
raft_print_z += height;
|
||||
raft_slice_z = raft_print_z - height / 2;
|
||||
m_object->add_tree_support_layer(layer_id++, height, raft_print_z, raft_slice_z);
|
||||
// ORCA: Guard tiny residual raft-to-object gaps so the EPSILON-biased ceil()
|
||||
// below cannot turn them into zero steps after floating-point accumulation.
|
||||
if (dist_to_go > EPSILON) {
|
||||
// ORCA: Bias by EPSILON so near-equal gaps do not get an extra split from FP noise.
|
||||
auto nsteps = int(ceil((dist_to_go - EPSILON) / m_slicing_params.max_suport_layer_height));
|
||||
double height = dist_to_go / nsteps;
|
||||
for (int i = 0; i < nsteps; ++i) {
|
||||
raft_print_z += height;
|
||||
raft_slice_z = raft_print_z - height / 2;
|
||||
m_object->add_tree_support_layer(layer_id++, height, raft_print_z, raft_slice_z);
|
||||
}
|
||||
}
|
||||
m_raft_layers = layer_id;
|
||||
}
|
||||
@@ -2076,9 +2081,10 @@ void TreeSupport::draw_circles()
|
||||
if (!area.empty()) has_circle_node = true;
|
||||
if (node.need_extra_wall) need_extra_wall = true;
|
||||
|
||||
// merge overhang to get a smoother interface surface
|
||||
// Do not merge when buildplate_only is on, because some underneath nodes may have been deleted.
|
||||
if (top_interface_layers > 0 && node.support_roof_layers_below > 0 && !on_buildplate_only && !node.is_sharp_tail) {
|
||||
// Merge the overhang into the roof area so tree tips can still produce
|
||||
// a continuous support interface. Suppressing this for build-plate-only
|
||||
// support drops the roof polygons entirely in valid tree branches.
|
||||
if (top_interface_layers > 0 && node.support_roof_layers_below > 0 && !node.is_sharp_tail) {
|
||||
ExPolygons overhang_expanded;
|
||||
if (node.overhang.contour.size() > 100 || node.overhang.holes.size()>1)
|
||||
overhang_expanded.emplace_back(node.overhang);
|
||||
@@ -2121,6 +2127,17 @@ void TreeSupport::draw_circles()
|
||||
roof_1st_layer = diff_ex(roof_1st_layer, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas,get_extents(roof_1st_layer)));
|
||||
roof_1st_layer = intersection_ex(roof_1st_layer, m_machine_border);
|
||||
|
||||
// Build-plate-only pruning can collapse the roof stack down to a single
|
||||
// printable layer. In that case we still need to emit an interface layer
|
||||
// instead of downgrading the last roof-adjacent layer to base support.
|
||||
if (on_buildplate_only && top_interface_layers > 0 && roof_areas.empty() && !roof_1st_layer.empty()) {
|
||||
append(roof_areas, roof_1st_layer);
|
||||
roof_1st_layer.clear();
|
||||
max_layers_above_roof = std::max(max_layers_above_roof, max_layers_above_roof1);
|
||||
max_layers_above_roof1 = 0;
|
||||
interface_id = obj_layer_nr % top_interface_layers;
|
||||
}
|
||||
|
||||
ExPolygons roofs; append(roofs, roof_1st_layer); append(roofs, roof_areas);append(roofs, roof_gap_areas);
|
||||
base_areas = diff_ex(base_areas, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roofs, get_extents(base_areas)));
|
||||
base_areas = intersection_ex(base_areas, m_machine_border);
|
||||
|
||||
@@ -898,6 +898,7 @@ public:
|
||||
size_t dtt_roof_tip;
|
||||
for (dtt_roof_tip = 0; dtt_roof_tip < roof_tip_layers && insert_layer_idx - dtt_roof_tip >= 1; ++ dtt_roof_tip) {
|
||||
size_t this_layer_idx = insert_layer_idx - dtt_roof_tip;
|
||||
const size_t roof_recovery_depth = dtt_roof_tip + supports_roof_layers;
|
||||
auto evaluateRoofWillGenerate = [&](const std::pair<Point, LineStatus> &p) {
|
||||
//FIXME Vojtech: The circle is just shifted, it has a known size, the infill should fit all the time!
|
||||
#if 0
|
||||
@@ -927,7 +928,9 @@ public:
|
||||
// don't move until
|
||||
roof_tip_layers - dtt_roof_tip,
|
||||
// supports roof
|
||||
dtt_roof_tip + supports_roof_layers > 0,
|
||||
roof_recovery_depth > 0,
|
||||
// recovered roof/contact depth for this slice
|
||||
roof_recovery_depth,
|
||||
// disable ovalization
|
||||
false);
|
||||
}
|
||||
@@ -942,9 +945,10 @@ public:
|
||||
roof_circle.translate(p.first);
|
||||
new_roofs.emplace_back(std::move(roof_circle));
|
||||
}
|
||||
this->add_roof(std::move(new_roofs), this_layer_idx, dtt_roof_tip + supports_roof_layers);
|
||||
this->add_roof(std::move(new_roofs), this_layer_idx, roof_recovery_depth);
|
||||
}
|
||||
|
||||
const size_t roof_recovery_depth = dtt_roof_tip + supports_roof_layers;
|
||||
for (const LineInformation &line : lines) {
|
||||
// If a line consists of enough tips, the assumption is that it is not a single tip, but part of a simulated support pattern.
|
||||
// Ovalisation should be disabled for these to improve the quality of the lines when tip_diameter=line_width
|
||||
@@ -954,14 +958,16 @@ public:
|
||||
// don't move until
|
||||
dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0,
|
||||
// supports roof
|
||||
dtt_roof_tip + supports_roof_layers > 0,
|
||||
roof_recovery_depth > 0,
|
||||
// recovered roof/contact depth for this slice
|
||||
roof_recovery_depth,
|
||||
disable_ovalistation);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// called by this->add_points_along_lines()
|
||||
void add_point_as_influence_area(std::pair<Point, LineStatus> p, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation)
|
||||
void add_point_as_influence_area(std::pair<Point, LineStatus> p, LayerIndex insert_layer, size_t dont_move_until, bool roof, size_t roof_recovery_dtt, bool skip_ovalisation)
|
||||
{
|
||||
bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE;
|
||||
bool gracious = to_bp || p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE;
|
||||
@@ -997,7 +1003,7 @@ private:
|
||||
state.supports_roof = roof;
|
||||
state.dont_move_until = dont_move_until;
|
||||
state.can_use_safe_radius = safe_radius;
|
||||
state.missing_roof_layers = force_tip_to_roof ? dont_move_until : 0;
|
||||
state.set_pending_roof_recovery(force_tip_to_roof ? dont_move_until : 0, roof_recovery_dtt);
|
||||
state.skip_ovalisation = skip_ovalisation;
|
||||
move_bounds[insert_layer].emplace_back(state, std::move(circle));
|
||||
}
|
||||
@@ -1095,10 +1101,9 @@ void finalize_raft_contact(
|
||||
// 1) Maximum num_support_roof_layers roof (top interface & contact) layers.
|
||||
// 2) Tree tips supporting either the roof layers or the object itself.
|
||||
// num_support_roof_layers should always be respected:
|
||||
// If num_support_roof_layers contact layers could not be produced, then the tree tip
|
||||
// is augmented with SupportElementState::missing_roof_layers
|
||||
// and the top "missing_roof_layers" of such particular tree tips are supposed to be coverted to
|
||||
// roofs aka interface layers by the tool path generator.
|
||||
// If the requested roof/contact stack cannot be generated directly, the affected tree tips
|
||||
// carry explicit pending roof recovery metadata so the sliced branch geometry can later be
|
||||
// promoted back to top contacts / interfaces at the correct contact depth.
|
||||
void sample_overhang_area(
|
||||
// Area to support
|
||||
Polygons &&overhang_area,
|
||||
@@ -1606,7 +1611,6 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di
|
||||
if (settings.increase_radius)
|
||||
current_elem.effective_radius_height += 1;
|
||||
coord_t radius = support_element_collision_radius(config, current_elem);
|
||||
|
||||
const auto _tiny_area_threshold = tiny_area_threshold();
|
||||
if (settings.move) {
|
||||
increased = relevant_offset;
|
||||
@@ -2059,7 +2063,10 @@ static void increase_areas_one_layer(
|
||||
out.supports_roof = first.supports_roof || second.supports_roof;
|
||||
out.dont_move_until = std::max(first.dont_move_until, second.dont_move_until);
|
||||
out.can_use_safe_radius = first.can_use_safe_radius || second.can_use_safe_radius;
|
||||
out.missing_roof_layers = std::min(first.missing_roof_layers, second.missing_roof_layers);
|
||||
// Preserve the deepest outstanding roof recovery request across merged sub-branches.
|
||||
out.set_pending_roof_recovery(
|
||||
std::max(first.missing_roof_layers, second.missing_roof_layers),
|
||||
std::max(first.roof_recovery_dtt, second.roof_recovery_dtt));
|
||||
out.skip_ovalisation = false;
|
||||
if (first.target_height > second.target_height) {
|
||||
out.target_height = first.target_height;
|
||||
@@ -3473,7 +3480,6 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons
|
||||
|
||||
// value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in draw_areas
|
||||
std::vector<SupportElements> move_bounds(num_support_layers);
|
||||
|
||||
// ### Place tips of the support tree
|
||||
for (size_t mesh_idx : processing.second)
|
||||
generate_initial_areas(*print.get_object(mesh_idx), volumes, config, overhangs,
|
||||
@@ -3591,7 +3597,33 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons
|
||||
// storage.support.generated = true;
|
||||
}
|
||||
|
||||
// Organic specific: Smooth branches and produce one cummulative mesh to be sliced.
|
||||
static void recover_pending_branch_roofs(
|
||||
InterfacePlacer &interface_placer,
|
||||
const std::vector<const SupportElement*> &branch_path,
|
||||
const LayerIndex layer_begin,
|
||||
std::vector<Polygons> &slices)
|
||||
{
|
||||
if (! interface_placer.support_parameters.has_top_contacts)
|
||||
return;
|
||||
|
||||
for (auto it = branch_path.rbegin(); it != branch_path.rend(); ++ it) {
|
||||
const SupportElement &el = **it;
|
||||
if (! el.state.has_pending_roof_recovery())
|
||||
break;
|
||||
|
||||
const LayerIndex slice_idx = el.state.layer_idx - layer_begin;
|
||||
if (slice_idx < 0 || slice_idx >= LayerIndex(slices.size()))
|
||||
continue;
|
||||
if (slices[size_t(slice_idx)].empty())
|
||||
continue;
|
||||
if (el.state.roof_recovery_dtt > interface_placer.support_parameters.num_top_interface_layers)
|
||||
continue;
|
||||
|
||||
interface_placer.add_roof(std::move(slices[size_t(slice_idx)]), el.state.layer_idx, el.state.roof_recovery_dtt);
|
||||
}
|
||||
}
|
||||
|
||||
// Organic specific: Smooth branches and produce one cumulative mesh to be sliced.
|
||||
void organic_draw_branches(
|
||||
PrintObject &print_object,
|
||||
TreeModelVolumes &volumes,
|
||||
@@ -3770,13 +3802,12 @@ void organic_draw_branches(
|
||||
// ++ ielement;
|
||||
}
|
||||
}
|
||||
|
||||
const SlicingParameters &slicing_params = print_object.slicing_parameters();
|
||||
MeshSlicingParams mesh_slicing_params;
|
||||
mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive;
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, trees.size(), 1),
|
||||
[&trees, &volumes, &config, &slicing_params, &move_bounds, &mesh_slicing_params, &throw_on_cancel](const tbb::blocked_range<size_t> &range) {
|
||||
[&trees, &volumes, &config, &slicing_params, &move_bounds, &mesh_slicing_params, &interface_placer, &throw_on_cancel](const tbb::blocked_range<size_t> &range) {
|
||||
indexed_triangle_set partial_mesh;
|
||||
std::vector<float> slice_z;
|
||||
std::vector<Polygons> bottom_contacts;
|
||||
@@ -3811,7 +3842,7 @@ void organic_draw_branches(
|
||||
num_empty = std::find_if(slices.begin(), slices.end(), [](auto &s) { return !s.empty(); }) - slices.begin();
|
||||
} else {
|
||||
if (branch.has_root) {
|
||||
if (branch.path.front()->state.to_model_gracious) {
|
||||
if (config.support_rests_on_model && branch.path.front()->state.to_model_gracious) {
|
||||
if (config.settings.support_floor_layers > 0)
|
||||
//FIXME one may just take the whole tree slice as bottom interface.
|
||||
bottom_contacts.emplace_back(intersection_clipped(slices.front(), volumes.getPlaceableAreas(0, layer_begin, [] {})));
|
||||
@@ -3860,7 +3891,7 @@ void organic_draw_branches(
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (config.settings.support_floor_layers > 0)
|
||||
if (config.support_rests_on_model && config.settings.support_floor_layers > 0)
|
||||
for (int i = int(bottom_extra_slices.size()) - 2; i >= 0; -- i)
|
||||
bottom_contacts.emplace_back(
|
||||
intersection_clipped(bottom_extra_slices[i].polygons, volumes.getPlaceableAreas(0, layer_begin - i - 1, [] {})));
|
||||
@@ -3872,19 +3903,7 @@ void organic_draw_branches(
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
//FIXME branch.has_tip seems to not be reliable.
|
||||
if (branch.has_tip && interface_placer.support_parameters.has_top_contacts)
|
||||
// Add top slices to top contacts / interfaces / base interfaces.
|
||||
for (int i = int(branch.path.size()) - 1; i >= 0; -- i) {
|
||||
const SupportElement &el = *branch.path[i];
|
||||
if (el.state.missing_roof_layers == 0)
|
||||
break;
|
||||
//FIXME Move or not?
|
||||
interface_placer.add_roof(std::move(slices[int(slices.size()) - i - 1]), el.state.layer_idx,
|
||||
interface_placer.support_parameters.num_top_interface_layers + 1 - el.state.missing_roof_layers);
|
||||
}
|
||||
#endif
|
||||
recover_pending_branch_roofs(interface_placer, branch.path, layer_begin, slices);
|
||||
}
|
||||
|
||||
layer_begin += LayerIndex(num_empty);
|
||||
|
||||
@@ -199,9 +199,20 @@ struct SupportElementState : public SupportElementStateBits
|
||||
AreaIncreaseSettings last_area_increase;
|
||||
|
||||
/*!
|
||||
* \brief Amount of roof layers that were not yet added, because the branch needed to move.
|
||||
* \brief Number of pending roof/contact recovery slices from this node downward, including this node.
|
||||
*/
|
||||
uint32_t missing_roof_layers;
|
||||
uint32_t missing_roof_layers = 0;
|
||||
|
||||
/*!
|
||||
* \brief Contact/interface depth that this node should recover when missing_roof_layers > 0.
|
||||
*/
|
||||
uint32_t roof_recovery_dtt = 0;
|
||||
|
||||
void set_pending_roof_recovery(uint32_t pending_layers, uint32_t recovery_depth)
|
||||
{
|
||||
this->missing_roof_layers = pending_layers;
|
||||
this->roof_recovery_dtt = pending_layers > 0 ? recovery_depth : 0;
|
||||
}
|
||||
|
||||
// called by increase_single_area() and increaseAreas()
|
||||
[[nodiscard]] static SupportElementState propagate_down(const SupportElementState &src)
|
||||
@@ -209,6 +220,10 @@ struct SupportElementState : public SupportElementStateBits
|
||||
SupportElementState dst{ src };
|
||||
++ dst.distance_to_top;
|
||||
-- dst.layer_idx;
|
||||
if (dst.has_pending_roof_recovery()) {
|
||||
-- dst.missing_roof_layers;
|
||||
++ dst.roof_recovery_dtt;
|
||||
}
|
||||
// set to invalid as we are a new node on a new layer
|
||||
dst.result_on_layer_reset();
|
||||
dst.skip_ovalisation = false;
|
||||
@@ -216,6 +231,7 @@ struct SupportElementState : public SupportElementStateBits
|
||||
}
|
||||
|
||||
[[nodiscard]] bool locked() const { return this->distance_to_top < this->dont_move_until; }
|
||||
[[nodiscard]] bool has_pending_roof_recovery() const { return this->missing_roof_layers > 0; }
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -343,7 +343,8 @@ public:
|
||||
}
|
||||
if (double dist_to_go = slicing_params.object_print_z_min - z; dist_to_go > EPSILON) {
|
||||
// Layers between the raft contacts and bottom of the object.
|
||||
auto nsteps = int(ceil(dist_to_go / slicing_params.max_suport_layer_height));
|
||||
// ORCA: Bias by EPSILON so near-equal gaps do not get an extra split from FP noise.
|
||||
auto nsteps = int(ceil((dist_to_go - EPSILON) / slicing_params.max_suport_layer_height));
|
||||
double step = dist_to_go / nsteps;
|
||||
for (int i = 0; i < nsteps; ++ i) {
|
||||
z += step;
|
||||
@@ -754,4 +755,4 @@ enum class LineStatus
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_TreeSupportCommon_hpp
|
||||
#endif // slic3r_TreeSupportCommon_hpp
|
||||
|
||||
@@ -71,8 +71,8 @@ namespace Timing {
|
||||
static TimeLimitAlarm new_nanos(uint64_t time_limit_nanoseconds, std::string_view limit_exceeded_message) {
|
||||
return TimeLimitAlarm(time_limit_nanoseconds, limit_exceeded_message);
|
||||
}
|
||||
static TimeLimitAlarm new_milis(uint64_t time_limit_milis, std::string_view limit_exceeded_message) {
|
||||
return TimeLimitAlarm(uint64_t(time_limit_milis) * 1000000l, limit_exceeded_message);
|
||||
static TimeLimitAlarm new_millis(uint64_t time_limit_millis, std::string_view limit_exceeded_message) {
|
||||
return TimeLimitAlarm(uint64_t(time_limit_millis) * 1000000l, limit_exceeded_message);
|
||||
}
|
||||
static TimeLimitAlarm new_seconds(uint64_t time_limit_seconds, std::string_view limit_exceeded_message) {
|
||||
return TimeLimitAlarm(uint64_t(time_limit_seconds) * 1000000000l, limit_exceeded_message);
|
||||
|
||||
Reference in New Issue
Block a user