Skip to content
Draft
5 changes: 5 additions & 0 deletions include/SkirtBrim.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ class SkirtBrim
void generate();

private:
/*!
* TODO: Document.
*/
coord_t estimateBrimNeeded(const Shape& shape);

/*!
* Plan the offsets which we will be going to perform and put them in the right order.
*
Expand Down
1 change: 1 addition & 0 deletions include/settings/EnumSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ enum class EPlatformAdhesion
{
SKIRT,
BRIM,
AUTOBRIM,
RAFT,
NONE,
PLUGIN,
Expand Down
3 changes: 2 additions & 1 deletion src/FffGcodeWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1172,7 +1172,7 @@ FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataS
const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings;
coord_t layer_thickness = mesh_group_settings.get<coord_t>("layer_height");
coord_t z;
bool include_helper_parts = true;
bool include_helper_parts = true; // NOTE/FIMXE: This will always be true, since the only place where it's set to false is (probably?) never executed. (See below.)
if (layer_nr < 0)
{
#ifdef DEBUG
Expand All @@ -1199,6 +1199,7 @@ FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataS
break;
}

// FIXME?: When would this ever be executed? `layer < 0` is already checked for up top, and the only layers that _are_ < 0 are raft?
if (layer_nr < 0 && mesh_group_settings.get<EPlatformAdhesion>("adhesion_type") == EPlatformAdhesion::RAFT)
{
include_helper_parts = false;
Expand Down
185 changes: 185 additions & 0 deletions src/SkirtBrim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#include "SkirtBrim.h"

#include <numeric> // for std::accumulate

#include <spdlog/spdlog.h>

#include "Application.h"
Expand Down Expand Up @@ -140,8 +142,191 @@ std::vector<SkirtBrim::Offset> SkirtBrim::generateBrimOffsetPlan(std::vector<Out
return all_brim_offsets;
}

void computeOverSegments(const Polygon& poly, const std::function<void(const Point2F&, const Point2F&)>& func)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense that this function is a method of the Polygon class (or even Polyline) ?
Also maybe with a more explicit naming.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and yes -- I wanted to have it self-contained for the spike, but we can move anything that is accepted to polygon/-line.

Any proposal for the name?

{
for (auto it = poly.beginSegments(); it != poly.endSegments(); ++it)
{
const Point2F p1((*it).start.X, (*it).start.Y);
const Point2F p2((*it).end.X, (*it).end.Y);
func(p1, p2);
}
}

bool compShapeProperties(const Shape& polys, Point2F& total_centroid, Point2F& total_moment)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe move to Shape or LinearAlg2D ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... on second thought, this is quite specific; I think I might just need to change the name.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I mean specifically this one -- the other one could still be moved.)

{
Point2F centroid(0.0f, 0.0f);
float accumulated_area = 0.0f;
const auto func_centroid = [&centroid, &accumulated_area](const Point2F& p1, const Point2F& p2)
{
const float cross2d = (p1.x_ * p2.y_ - p1.y_ * p2.x_);
accumulated_area += cross2d;
centroid.x_ += (p1.x_ + p2.x_) * accumulated_area;
centroid.y_ += (p1.y_ + p2.y_) * accumulated_area;
};
Point2F moment(0.0f, 0.0f);
const auto func_second_moment = [&moment](const Point2F& p1, const Point2F& p2)
{
const float mul = (p1.x_ * p2.y_ - p1.y_ * p2.x_) / 12.0f;
moment.x_ += (p1.y_ * p1.y_ + p1.y_ * p2.y_ + p2.y_ * p2.y_) * mul;
moment.y_ += (p1.x_ * p1.x_ + p1.x_ * p2.x_ + p2.x_ * p2.x_) * mul;
};

for (const auto& poly : polys)
{
const auto area = poly.area();
if (poly.getPoints().size() < 3 || std::abs(area) < EPSILON)
{
continue;
}

// Centroid:
centroid = Point2F(0.0f, 0.0f);
accumulated_area = 0.0f;
computeOverSegments(poly, func_centroid);
accumulated_area *= 3.0f;
centroid.x_ /= accumulated_area;
centroid.y_ /= accumulated_area;
total_centroid.x_ += centroid.x_ * area;
total_centroid.y_ += centroid.y_ * area;

// Moment:
moment = Point2F(0.0f, 0.0f);
computeOverSegments(poly, func_second_moment);
const float simple_or_hole = poly.orientation() ? -1.0f : 1.0f;
total_moment.x_ += simple_or_hole * moment.x_;
total_moment.y_ += simple_or_hole * moment.y_;
}

total_moment.x_ -= total_centroid.y_ * total_centroid.y_;
total_moment.y_ -= total_centroid.x_ * total_centroid.x_;
const auto area = polys.area();
total_centroid.x_ /= area;
total_centroid.y_ /= area;

return area > EPSILON;
}

float getThermalLength(const std::vector<Mesh>& meshes)
{
constexpr float min_length = 1250.0f;
return std::accumulate(
meshes.begin(),
meshes.end(),
Comment thread
rburema marked this conversation as resolved.
min_length,
[](const float& thermal_length, const Mesh& mesh)
{
constexpr float default_length = 200.0f;
const float res = default_length; // TODO: make into setting! (depends on extruder of the mesh, which has the material)
return std::min(default_length, res);
Comment thread
wawanbreton marked this conversation as resolved.
});
}

float getMaxSpeed(const std::vector<Mesh>& meshes)
{
constexpr std::array<std::string_view, 10> speed_settings = {
"speed_infill", "speed_wall_0", "speed_wall_x", "speed_wall_0_roofing", "speed_wall_x_roofing",
"speed_wall_0_flooring", "speed_wall_x_flooring", "speed_roofing", "speed_flooring", "speed_topbottom",
/* // and for support (TODO; handle this separately):
"speed_support_infill",
"speed_support_roof",
"speed_support_bottom",
*/
};

const float res = std::accumulate(
meshes.begin(),
meshes.end(),
-1.0f,
[](const float& thermal_length, const Mesh& mesh)
{
return std::accumulate(
speed_settings.begin(),
speed_settings.end(),
-1.0f,
[&mesh](const float& value, const std::string_view& setting_name)
{
return std::max(value, static_cast<float>(mesh.settings_.get<coord_t>(setting_name.data())));
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using accumulate in this case is quite confusing 🤔 could you use ranges::max_element instead, with a projection function ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I was trying to find that function 2 weeks ago. Yes, we should use that instead.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... and that way I just noticed I missed the parameter in the 1st accumulate (and misnamed it), making it only return the max speed of the last mesh. So it's a good thing I'm rewriting it 😅

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... except now I'm trying to rewrite it as max_element, and even when you use a projection, you get the max element out, then have to project it again to get the actual value out, so I do think accumulate makes sense in this case (it helps if you see it as fold instead -- accumulate is a bit of a misnomer to begin with).


constexpr float default_speed = 250.0f;
return res < 0.0f ? default_speed : res;
}

coord_t SkirtBrim::estimateBrimNeeded(const Shape& shape)
{
/* NOTE: Brim-size estimation is largely copied from 'OrcaSlicer/src/libslic3r/Model.cpp' at the moment.
* Their code claims to take the delta-temperature into account, but the deltaT parameter is never used.
* Also the function is called with an extruder-temp, but the comment (+ the delta name) says it's a difference.
* So (besides a heavy rewrite to put it all into our code) that was left out.
*/

const auto& meshes = Application::getInstance().current_slice_->scene.current_mesh_group->meshes;

// Get adhesion, etc. from settings.
const float adhesion_coefficient = 1.0f; // TODO: Get from settings.
const float max_speed = getMaxSpeed(meshes);

// Get height of the mesh-group.
const AABB3D aabb = std::accumulate(
meshes.begin(),
meshes.end(),
AABB3D(),
[](const AABB3D& aabb, const Mesh& mesh)
{
return AABB3D(mesh.getAABB()).include(aabb);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can see, mesh.getAABB() return a AABB3D, so why the conversion ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that might just be a leftover from when I had written something else there instead.

});
const float max_height = INT2MM(aabb.max_.z_ - aabb.min_.z_);

// Calculate the second moment of the outline(s) of the first layer.
Point2F centroid(0.0f, 0.0f);
Point2F second_moment(0.0f, 0.0f);
if (! compShapeProperties(shape, centroid, second_moment))
{
return 0.0f;
}
second_moment.x_ = INT2MM2(INT2MM2(second_moment.x_));
second_moment.y_ = INT2MM2(INT2MM2(second_moment.y_));

// Thermal lenght stuff.
Comment thread
rburema marked this conversation as resolved.
Outdated
const Point2F width_depth(aabb.max_.x_ - aabb.min_.x_, aabb.max_.y_ - aabb.min_.y_);
Comment thread
rburema marked this conversation as resolved.
Outdated
const float thermal_length = INT2MM(width_depth.vSize());
const float thermal_length_ref = getThermalLength(meshes);

// Calculate the result.
constexpr float height_to_area_normalization = 1920.0f;
const float height_to_area
= std::max(max_height / second_moment.x_ * INT2MM(width_depth.y_), max_height / second_moment.y_ * INT2MM(width_depth.x_)) * max_height / height_to_area_normalization;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can either second_moment.x_ or second_moment.y_ be null anyhow ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I think we should guard against that in any case. But that would be an object that'd require an infinitely large brim then 😄

I think I see another (small) division by zero potential in the accumulate_areas above as well now that you've pointed out this one.

constexpr float thermal_lenght_gain = 8.0f;
constexpr float thermal_height_saturation_mm = 30.0f;
constexpr float brim_width_max_mm = 18.0f;
constexpr float brim_width_footprint_mul_max = 1.5f;
const float thermal_length_mult = thermal_length * brim_width_footprint_mul_max;
float res = thermal_length * thermal_lenght_gain / thermal_length_ref * std::min(max_height, thermal_height_saturation_mm) / thermal_height_saturation_mm;
res = std::min(std::min(std::max(height_to_area * max_speed, res), brim_width_max_mm), thermal_length_mult) * adhesion_coefficient;

// Clamp, convert & return.
constexpr float brim_width_min_check_mm = 5.0f;
return MM2INT(res < std::min(brim_width_min_check_mm, thermal_length_mult) ? 0 : std::min(brim_width_max_mm, res));
}

void SkirtBrim::generate()
{
// TODO: multiple layers? helper structures? etc.?
constexpr bool include_support = false;
constexpr bool include_prime_tower = false;
coord_t estimated_brim_width = 0;
if (adhesion_type_ == EPlatformAdhesion::AUTOBRIM)
{
estimated_brim_width = estimateBrimNeeded(storage_.getLayerOutlines(0, include_support, include_prime_tower));

std::fprintf(stderr, "ESTIMATED BRIM WIDTH (MICRON) %d\n", estimated_brim_width);

// TODO: plus/minus the gap, then divide by line-width
}
// TODO: handle estimated brim-width w.r.t. extruder-line counts? -> probably just ignore what's filled in? or do the ratios need to match
// TODO: handle the estimation per mesh?

std::vector<Outline> starting_outlines(extruder_count_);
std::vector<Offset> all_brim_offsets = generateBrimOffsetPlan(starting_outlines);
std::vector<Shape> allowed_areas_per_extruder = generateAllowedAreas(starting_outlines);
Expand Down
2 changes: 2 additions & 0 deletions src/settings/Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,8 @@ EPlatformAdhesion Settings::get<EPlatformAdhesion>(const std::string& key) const
return EPlatformAdhesion::SKIRT;
case "brim"_sw:
return EPlatformAdhesion::BRIM;
case "autobrim"_sw:
return EPlatformAdhesion::AUTOBRIM;
case "raft"_sw:
return EPlatformAdhesion::RAFT;
case "none"_sw:
Expand Down
Loading