Initial port of Brim ear gizmo

Cherry-picked from bambulab/BambuStudio@92c85a13d0

Co-authored-by: Mack <yongfang.bian@bambulab.com>
This commit is contained in:
Noisyfox 2025-02-16 18:22:08 +08:00
parent 830c1ac928
commit 185fb3cb26
28 changed files with 1595 additions and 65 deletions

View file

@ -806,8 +806,8 @@ double configBrimWidthByVolumeGroups(double adhesion, double maxSpeed, const std
// Generate ears
// Ported from SuperSlicer: https://github.com/supermerill/SuperSlicer/blob/45d0532845b63cd5cefe7de7dc4ef0e0ed7e030a/src/libslic3r/Brim.cpp#L1116
static ExPolygons make_brim_ears(ExPolygons& obj_expoly, coord_t size_ear, coord_t ear_detection_length,
coordf_t brim_ears_max_angle, bool is_outer_brim) {
static ExPolygons make_brim_ears_auto(const ExPolygons& obj_expoly, coord_t size_ear, coord_t ear_detection_length,
coordf_t brim_ears_max_angle, bool is_outer_brim) {
ExPolygons mouse_ears_ex;
if (size_ear <= 0) {
return mouse_ears_ex;
@ -815,7 +815,7 @@ static ExPolygons make_brim_ears(ExPolygons& obj_expoly, coord_t size_ear, coord
// Detect places to put ears
const coordf_t angle_threshold = (180 - brim_ears_max_angle) * PI / 180.0;
Points pt_ears;
for (ExPolygon &poly : obj_expoly) {
for (const ExPolygon &poly : obj_expoly) {
Polygon decimated_polygon = poly.contour;
if (ear_detection_length > 0) {
// decimate polygon
@ -835,8 +835,8 @@ static ExPolygons make_brim_ears(ExPolygons& obj_expoly, coord_t size_ear, coord
// Then add ears
// create ear pattern
Polygon point_round;
for (size_t i = 0; i < POLY_SIDES; i++) {
double angle = (2.0 * PI * i) / POLY_SIDES;
for (size_t i = 0; i < POLY_SIDE_COUNT; i++) {
double angle = (2.0 * PI * i) / POLY_SIDE_COUNT;
point_round.points.emplace_back(size_ear * cos(angle), size_ear * sin(angle));
}
@ -850,6 +850,41 @@ static ExPolygons make_brim_ears(ExPolygons& obj_expoly, coord_t size_ear, coord
return mouse_ears_ex;
}
static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWidth, float brim_offset, Flow &flow, bool is_outer_brim)
{
ExPolygons mouse_ears_ex;
BrimPoints brim_ear_points = object->model_object()->brim_points;
if (brim_ear_points.size() <= 0) {
return mouse_ears_ex;
}
const Geometry::Transformation& trsf = object->model_object()->instances[0]->get_transformation();
Transform3d model_trsf = trsf.get_matrix_no_offset();
const Point &center_offset = object->center_offset();
model_trsf = model_trsf.pretranslate(Vec3d(- unscale<double>(center_offset.x()), - unscale<double>(center_offset.y()), 0));
for (auto &pt : brim_ear_points) {
Vec3f world_pos = pt.transform(trsf.get_matrix());
if ( world_pos.z() > 0) continue;
Polygon point_round;
float brim_width = floor(scale_(pt.head_front_radius) / flowWidth / 2) * flowWidth * 2;
if (is_outer_brim) {
double flowWidthScale = flowWidth / SCALING_FACTOR;
brim_width = floor(brim_width / flowWidthScale / 2) * flowWidthScale * 2;
}
coord_t size_ear = (brim_width - brim_offset - flow.scaled_spacing());
for (size_t i = 0; i < POLY_SIDE_COUNT; i++) {
double angle = (2.0 * PI * i) / POLY_SIDE_COUNT;
point_round.points.emplace_back(size_ear * cos(angle), size_ear * sin(angle));
}
mouse_ears_ex.emplace_back();
mouse_ears_ex.back().contour = point_round;
Vec3f pos = pt.transform(model_trsf);
int32_t pt_x = scale_(pos.x());
int32_t pt_y = scale_(pos.y());
mouse_ears_ex.back().contour.translate(Point(pt_x, pt_y));
}
return mouse_ears_ex;
}
//BBS: create all brims
static ExPolygons outer_inner_brim_area(const Print& print,
const float no_brim_offset, std::map<ObjectID, ExPolygons>& brimAreaMap,
@ -888,9 +923,10 @@ static ExPolygons outer_inner_brim_area(const Print& print,
const float scaled_additional_brim_width = scale_(floor(5 / flowWidth / 2) * flowWidth * 2);
const float scaled_half_min_adh_length = scale_(1.1);
bool has_brim_auto = object->config().brim_type == btAutoBrim;
const bool use_brim_ears = object->config().brim_type == btEar;
const bool has_inner_brim = brim_type == btInnerOnly || brim_type == btOuterAndInner || use_brim_ears;
const bool has_outer_brim = brim_type == btOuterOnly || brim_type == btOuterAndInner || brim_type == btAutoBrim || use_brim_ears;
const bool use_auto_brim_ears = object->config().brim_type == btEar;
const bool use_brim_ears = object->config().brim_type == btPainted;
const bool has_inner_brim = brim_type == btInnerOnly || brim_type == btOuterAndInner || use_auto_brim_ears || use_brim_ears;
const bool has_outer_brim = brim_type == btOuterOnly || brim_type == btOuterAndInner || brim_type == btAutoBrim || use_auto_brim_ears || use_brim_ears;
coord_t ear_detection_length = scale_(object->config().brim_ears_detection_length.value);
coordf_t brim_ears_max_angle = object->config().brim_ears_max_angle.value;
@ -951,27 +987,30 @@ static ExPolygons outer_inner_brim_area(const Print& print,
if (has_outer_brim) {
// BBS: inner and outer boundary are offset from the same polygon incase of round off error.
auto innerExpoly = offset_ex(ex_poly.contour, brim_offset, jtRound, SCALED_RESOLUTION);
auto &clipExpoly = innerExpoly;
ExPolygons outerExpoly;
if (use_brim_ears) {
outerExpoly = make_brim_ears(object, flowWidth, brim_offset, flow, true);
//outerExpoly = offset_ex(outerExpoly, brim_width_mod, jtRound, SCALED_RESOLUTION);
} else if (use_auto_brim_ears) {
coord_t size_ear = (brim_width_mod - brim_offset - flow.scaled_spacing());
append(brim_area_object, diff_ex(make_brim_ears(innerExpoly, size_ear, ear_detection_length, brim_ears_max_angle, true), clipExpoly));
} else {
// Normal brims
append(brim_area_object, diff_ex(offset_ex(innerExpoly, brim_width_mod, jtRound, SCALED_RESOLUTION), clipExpoly));
outerExpoly = make_brim_ears_auto(innerExpoly, size_ear, ear_detection_length, brim_ears_max_angle, true);
}else {
outerExpoly = offset_ex(innerExpoly, brim_width_mod, jtRound, SCALED_RESOLUTION);
}
append(brim_area_object, diff_ex(outerExpoly, innerExpoly));
}
if (has_inner_brim) {
auto outerExpoly = offset_ex(ex_poly_holes_reversed, -brim_offset);
auto clipExpoly = offset_ex(ex_poly_holes_reversed, -brim_width - brim_offset);
ExPolygons outerExpoly;
auto innerExpoly = offset_ex(ex_poly_holes_reversed, -brim_width - brim_offset);
if (use_brim_ears) {
outerExpoly = make_brim_ears(object, flowWidth, brim_offset, flow, false);
} else if (use_auto_brim_ears) {
coord_t size_ear = (brim_width - brim_offset - flow.scaled_spacing());
append(brim_area_object, diff_ex(make_brim_ears(outerExpoly, size_ear, ear_detection_length, brim_ears_max_angle, false), clipExpoly));
} else {
// Normal brims
append(brim_area_object, diff_ex(outerExpoly, clipExpoly));
outerExpoly = make_brim_ears_auto(offset_ex(ex_poly_holes_reversed, -brim_offset), size_ear, ear_detection_length, brim_ears_max_angle, false);
}else {
outerExpoly = offset_ex(ex_poly_holes_reversed, -brim_offset);
}
append(brim_area_object, diff_ex(outerExpoly, innerExpoly));
}
if (!has_inner_brim) {
// BBS: brim should be apart from holes

View file

@ -0,0 +1,63 @@
#ifndef BRIMEARSPOINT_HPP
#define BRIMEARSPOINT_HPP
#include <libslic3r/Point.hpp>
namespace Slic3r {
// An enum to keep track of where the current points on the ModelObject came from.
enum class PointsStatus {
NoPoints, // No points were generated so far.
Generating, // The autogeneration algorithm triggered, but not yet finished.
AutoGenerated, // Points were autogenerated (i.e. copied from the backend).
UserModified // User has done some edits.
};
struct BrimPoint
{
Vec3f pos;
float head_front_radius;
BrimPoint()
: pos(Vec3f::Zero()), head_front_radius(0.f)
{}
BrimPoint(float pos_x,
float pos_y,
float pos_z,
float head_radius)
: pos(pos_x, pos_y, pos_z)
, head_front_radius(head_radius)
{}
BrimPoint(Vec3f position, float head_radius)
: pos(position)
, head_front_radius(head_radius)
{}
Vec3f transform(const Transform3d &trsf)
{
Vec3d result = trsf * pos.cast<double>();
return result.cast<float>();
}
bool operator==(const BrimPoint &sp) const
{
float rdiff = std::abs(head_front_radius - sp.head_front_radius);
return (pos == sp.pos) && rdiff < float(EPSILON);
}
bool operator!=(const BrimPoint &sp) const { return !(sp == (*this)); }
template<class Archive> void serialize(Archive &ar)
{
ar(pos, head_front_radius);
}
};
using BrimPoints = std::vector<BrimPoint>;
}
#endif // BRIMEARSPOINT_HPP

View file

@ -43,6 +43,7 @@ set(lisbslic3r_sources
FaceDetector.hpp
Brim.cpp
Brim.hpp
BrimEarsPoint.hpp
BuildVolume.cpp
BuildVolume.hpp
Circle.cpp

View file

@ -185,6 +185,15 @@ bool overlaps(const ExPolygons& expolys1, const ExPolygons& expolys2)
return false;
}
bool overlaps(const ExPolygons& expolys, const ExPolygon& expoly)
{
for (const ExPolygon& el : expolys) {
if (el.overlaps(expoly))
return true;
}
return false;
}
Point projection_onto(const ExPolygons& polygons, const Point& from)
{
Point projected_pt;

View file

@ -457,6 +457,7 @@ inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double toleranc
bool expolygons_match(const ExPolygon &l, const ExPolygon &r);
bool overlaps(const ExPolygons& expolys1, const ExPolygons& expolys2);
bool overlaps(const ExPolygons& expolys, const ExPolygon& expoly);
Point projection_onto(const ExPolygons& expolys, const Point& pt);

View file

@ -1063,6 +1063,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs)
this->sla_support_points = rhs.sla_support_points;
this->sla_points_status = rhs.sla_points_status;
this->sla_drain_holes = rhs.sla_drain_holes;
this->brim_points = rhs.brim_points;
this->layer_config_ranges = rhs.layer_config_ranges;
this->layer_height_profile = rhs.layer_height_profile;
this->printable = rhs.printable;
@ -1102,6 +1103,7 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs)
this->sla_support_points = std::move(rhs.sla_support_points);
this->sla_points_status = std::move(rhs.sla_points_status);
this->sla_drain_holes = std::move(rhs.sla_drain_holes);
this->brim_points = std::move(brim_points);
this->layer_config_ranges = std::move(rhs.layer_config_ranges);
this->layer_height_profile = std::move(rhs.layer_height_profile);
this->printable = std::move(rhs.printable);
@ -1736,6 +1738,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con
new_object->sla_support_points.clear();
new_object->sla_drain_holes.clear();
new_object->sla_points_status = sla::PointsStatus::NoPoints;
new_object->brim_points.clear();
new_object->clear_volumes();
new_object->input_file.clear();
@ -2040,6 +2043,7 @@ ModelObjectPtrs ModelObject::merge_volumes(std::vector<int>& vol_indeces)
upper->sla_support_points.clear();
upper->sla_drain_holes.clear();
upper->sla_points_status = sla::PointsStatus::NoPoints;
upper->brim_points.clear();
upper->clear_volumes();
upper->input_file.clear();
@ -3510,6 +3514,17 @@ bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObjec
[](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mmu_segmentation_facets.timestamp_matches(mv_new.mmu_segmentation_facets); });
}
bool model_brim_points_data_changed(const ModelObject& mo, const ModelObject& mo_new)
{
if (mo.brim_points.size() != mo_new.brim_points.size())
return true;
for (size_t i = 0; i < mo.brim_points.size(); ++i) {
if (mo.brim_points[i] != mo_new.brim_points[i])
return true;
}
return false;
}
bool model_has_multi_part_objects(const Model &model)
{
for (const ModelObject *model_object : model.objects)

View file

@ -10,6 +10,7 @@
#include "Slicing.hpp"
#include "SLA/SupportPoint.hpp"
#include "SLA/Hollowing.hpp"
#include "BrimEarsPoint.hpp"
#include "TriangleMesh.hpp"
#include "CustomGCode.hpp"
#include "calib.hpp"
@ -383,9 +384,7 @@ public:
// Holes to be drilled into the object so resin can flow out
sla::DrainHoles sla_drain_holes;
// Connectors to be added into the object before cut and are used to create a solid/negative volumes during a cut perform
CutConnectors cut_connectors;
CutObjectBase cut_id;
BrimPoints brim_points;
/* This vector accumulates the total translation applied to the object by the
center_around_origin() method. Callers might want to apply the same translation
@ -396,6 +395,10 @@ public:
// BBS: save for compare with new load volumes
std::vector<ObjectID> volume_ids;
// Connectors to be added into the object before cut and are used to create a solid/negative volumes during a cut perform
CutConnectors cut_connectors;
CutObjectBase cut_id;
Model* get_model() { return m_model; }
const Model* get_model() const { return m_model; }
// BBS: production extension
@ -673,7 +676,7 @@ private:
Internal::StaticSerializationWrapper<ModelConfigObject const> config_wrapper(config);
Internal::StaticSerializationWrapper<LayerHeightProfile const> layer_heigth_profile_wrapper(layer_height_profile);
ar(name, module_name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper,
sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation,
sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, brim_points,
m_bounding_box_approx, m_bounding_box_approx_valid,
m_bounding_box_exact, m_bounding_box_exact_valid, m_min_max_z_valid,
m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid,
@ -686,7 +689,7 @@ private:
// BBS: add backup, check modify
SaveObjectGaurd gaurd(*this);
ar(name, module_name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper,
sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation,
sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, brim_points,
m_bounding_box_approx, m_bounding_box_approx_valid,
m_bounding_box_exact, m_bounding_box_exact_valid, m_min_max_z_valid,
m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid,
@ -1694,6 +1697,8 @@ bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo
// The function assumes that volumes list is synchronized.
extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new);
bool model_brim_points_data_changed(const ModelObject& mo, const ModelObject& mo_new);
// If the model has multi-part objects, then it is currently not supported by the SLA mode.
// Either the model cannot be loaded, or a SLA printer has to be activated.
bool model_has_multi_part_objects(const Model &model);

View file

@ -1256,6 +1256,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER);
bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty());
bool model_origin_translation_differ = model_object.origin_translation != model_object_new.origin_translation;
bool brim_points_differ = model_brim_points_data_changed(model_object, model_object_new);
auto print_objects_range = print_object_status_db.get_range(model_object);
// The list actually can be empty if all instances are out of the print bed.
//assert(print_objects_range.begin() != print_objects_range.end());
@ -1302,6 +1303,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
} else if (model_custom_seam_data_changed(model_object, model_object_new)) {
update_apply_status(this->invalidate_step(psGCodeExport));
}
if (brim_points_differ) {
model_object.brim_points = model_object_new.brim_points;
update_apply_status(this->invalidate_all_steps());
}
}
if (! solid_or_modifier_differ) {
// Synchronize Object's config.

View file

@ -327,6 +327,7 @@ static const t_config_enum_values s_keys_map_BrimType = {
{"outer_and_inner", btOuterAndInner},
{"auto_brim", btAutoBrim}, // BBS
{"brim_ears", btEar}, // Orca
{"painted", btPainted}, // BBS
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BrimType)
@ -1257,12 +1258,14 @@ void PrintConfigDef::init_fff_params()
def->enum_keys_map = &ConfigOptionEnum<BrimType>::get_enum_values();
def->enum_values.emplace_back("auto_brim");
def->enum_values.emplace_back("brim_ears");
def->enum_values.emplace_back("painted");
def->enum_values.emplace_back("outer_only");
def->enum_values.emplace_back("inner_only");
def->enum_values.emplace_back("outer_and_inner");
def->enum_values.emplace_back("no_brim");
def->enum_labels.emplace_back(L("Auto"));
def->enum_labels.emplace_back(L("Mouse ear"));
def->enum_labels.emplace_back(L("Painted"));
def->enum_labels.emplace_back(L("Outer brim only"));
def->enum_labels.emplace_back(L("Inner brim only"));
def->enum_labels.emplace_back(L("Outer and inner brim"));

View file

@ -226,6 +226,7 @@ enum SLAPillarConnectionMode {
enum BrimType {
btAutoBrim, // BBS
btEar, // Orca
btPainted, // BBS
btOuterOnly,
btInnerOnly,
btOuterAndInner,

View file

@ -65,9 +65,8 @@ static constexpr double LARGE_BED_THRESHOLD = 2147;
static constexpr size_t MAXIMUM_EXTRUDER_NUMBER = 64;
extern double SCALING_FACTOR;
// for creating circles (for brim_ear)
#define POLY_SIDES 24
static constexpr double PI = 3.141592653589793238;
#define POLY_SIDE_COUNT 24 // for brim ear circle
// When extruding a closed loop, the loop is interrupted and shortened a bit to reduce the seam.
// SoftFever: replaced by seam_gap now
// static constexpr double LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER = 0.15;