Merge branch 'main' into feature/wipetower2_travel_path

This commit is contained in:
SoftFever
2026-08-05 00:11:07 +08:00
61 changed files with 1808 additions and 615 deletions
+2 -1
View File
@@ -31,8 +31,9 @@ namespace AABBTreeLines {
inline VectorType closest_point_to_origin(size_t primitive_index, ScalarType& squared_distance) const
{
Vec<LineType::Dim, typename LineType::Scalar> nearest_point;
Vec<LineType::Dim, typename LineType::Scalar> cast_origin = origin.template cast<typename LineType::Scalar>();
const LineType& line = lines[primitive_index];
squared_distance = line_alg::distance_to_squared(line, origin.template cast<typename LineType::Scalar>(), &nearest_point);
squared_distance = line_alg::distance_to_squared(line, cast_origin, &nearest_point);
return nearest_point.template cast<ScalarType>();
}
};
+1 -1
View File
@@ -25,7 +25,7 @@ public:
min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
template<class It, class = IteratorOnly<It>>
BoundingBoxBase(It from, It to)
BoundingBoxBase(It from, It to) : BoundingBoxBase()
{ construct(*this, from, to); }
BoundingBoxBase(const PointsType &points)
+6 -10
View File
@@ -349,7 +349,7 @@ static ExPolygons make_brim_ears_auto(const ExPolygons& obj_expoly, coord_t size
return mouse_ears_ex;
}
static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWidth, float brim_offset, Flow &flow, bool is_outer_brim)
static ExPolygons make_brim_ears(const PrintObject* object)
{
ExPolygons mouse_ears_ex;
BrimPoints brim_ear_points = object->model_object()->brim_points;
@@ -373,12 +373,7 @@ static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWi
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());
const coord_t size_ear = scale_(pt.head_front_radius);
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));
@@ -452,7 +447,8 @@ static ExPolygons outer_inner_brim_area(const Print& print,
bool has_brim_auto = object->config().brim_type == btAutoBrim;
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 use_inner_brim_ears = (use_auto_brim_ears || use_brim_ears) && !object->config().brim_ears_outer_only.value;
const bool has_inner_brim = brim_type == btInnerOnly || brim_type == btOuterAndInner || use_inner_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;
@@ -531,7 +527,7 @@ static ExPolygons outer_inner_brim_area(const Print& print,
auto innerExpoly = offset_ex(ex_poly.contour, brim_offset, jtRound, SCALED_RESOLUTION);
ExPolygons outerExpoly;
if (use_brim_ears) {
outerExpoly = make_brim_ears(object, flowWidth, brim_offset, flow, true);
outerExpoly = make_brim_ears(object);
//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());
@@ -545,7 +541,7 @@ static ExPolygons outer_inner_brim_area(const Print& print,
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);
outerExpoly = make_brim_ears(object);
} else if (use_auto_brim_ears) {
coord_t size_ear = (brim_width - brim_offset - flow.scaled_spacing());
outerExpoly = make_brim_ears_auto(offset_ex(ex_poly_holes_reversed, -brim_offset), size_ear, ear_detection_length, brim_ears_max_angle, false);
+12
View File
@@ -278,6 +278,9 @@ struct SurfaceFillParams
// For Gyroid: when true, use the parameterized "optimized" wave.
bool gyroid_optimized = false;
// Orca: corner smoothing factor in the range [0, 1].
double smooth_factor { 0. };
CenterOfSurfacePattern center_of_surface_pattern{CenterOfSurfacePattern::Each_Surface};
bool separated_infills{false};
@@ -316,6 +319,7 @@ struct SurfaceFillParams
RETURN_COMPARE_NON_EQUAL(skin_infill_depth);
RETURN_COMPARE_NON_EQUAL(infill_overhang_angle);
RETURN_COMPARE_NON_EQUAL(gyroid_optimized);
RETURN_COMPARE_NON_EQUAL(smooth_factor);
RETURN_COMPARE_NON_EQUAL(center_of_surface_pattern);
RETURN_COMPARE_NON_EQUAL(separated_infills);
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, fill_order);
@@ -348,6 +352,7 @@ struct SurfaceFillParams
this->center_of_surface_pattern == rhs.center_of_surface_pattern &&
this->separated_infills == rhs.separated_infills &&
this->gyroid_optimized == rhs.gyroid_optimized &&
this->smooth_factor == rhs.smooth_factor &&
this->fill_order == rhs.fill_order;
}
};
@@ -964,6 +969,11 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.infill_direction.value,
region_config.sparse_infill_rotate_template.value);
params.fixed_angle = !region_config.sparse_infill_rotate_template.value.empty();
// Orca: special case; apply smoothing factor only for Hilbert Curve sparse infill.
// FillHilbertCurve::generate clamps and validates the value itself.
if (params.pattern == ipHilbertCurve)
params.smooth_factor = 0.01 * region_config.sparse_infill_smooth_factor.value;
} else {
const bool top_layer_direction_set = surface.is_top() && region_config.top_layer_direction.value >= 0.;
const bool bottom_layer_direction_set = surface.is_bottom() && region_config.bottom_layer_direction.value >= 0.;
@@ -1328,6 +1338,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
params.lateral_lattice_angle_2 = surface_fill.params.lateral_lattice_angle_2;
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
params.smooth_factor = surface_fill.params.smooth_factor;
// BBS
params.flow = surface_fill.params.flow;
@@ -1569,6 +1580,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
params.multiline = surface_fill.params.multiline;
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
params.smooth_factor = surface_fill.params.smooth_factor;
for (ExPolygon &expoly : surface_fill.expolygons) {
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
+5 -5
View File
@@ -1857,12 +1857,12 @@ static inline void base_support_extend_infill_lines(Polylines &infill, BoundaryI
const bool first = graph.first(cp);
int extend_next_idx = -1;
int extend_prev_idx = -1;
coord_t dist_y_prev;
coord_t dist_y_next;
double arc_len_prev;
double arc_len_next;
coord_t dist_y_prev = 0;
coord_t dist_y_next = 0;
double arc_len_prev = 0;
double arc_len_next = 0;
if (! graph.next_vertical(cp)){
if (! graph.next_vertical(cp)) {
size_t i = cp.point_idx;
size_t j = next_idx_modulo(i, contour);
while (j != cp.next_on_contour->point_idx) {
+3
View File
@@ -82,6 +82,9 @@ struct FillParams
// For Gyroid: when true, use the parameterized "optimized" variant.
bool gyroid_optimized { false };
// Orca: corner smoothing factor in the range [0, 1].
double smooth_factor { 0. };
// For Lateral lattice
coordf_t lateral_lattice_angle_1 { 0.f };
coordf_t lateral_lattice_angle_2 { 0.f };
+161 -2
View File
@@ -114,12 +114,12 @@ void FillPlanePath::_fill_surface_single(
// Filling in a bounding box over the whole object, clip generated polyline against the snug bounding box.
snug_bounding_box.translate(-shift.x(), -shift.y());
InfillPolylineClipper output(snug_bounding_box, distance_between_lines);
this->generate(min_x, min_y, max_x, max_y, resolution, output);
this->generate(min_x, min_y, max_x, max_y, resolution, params, output);
polyline.points = std::move(output.result());
} else {
// Filling in a snug bounding box, no need to clip.
InfillPolylineOutput output(distance_between_lines);
this->generate(min_x, min_y, max_x, max_y, resolution, output);
this->generate(min_x, min_y, max_x, max_y, resolution, params, output);
polyline.points = std::move(output.result());
}
}
@@ -288,6 +288,147 @@ static void generate_hilbert_curve(coord_t min_x, coord_t min_y, coord_t max_x,
}
}
using QuinticBezier = std::array<Vec2d, 6>;
static bool is_bezier_flat(const QuinticBezier &curve, const double deviation)
{
// A Bezier curve stays inside the convex hull of its control points. Therefore, keeping every
// control point within a deviation-wide strip around the endpoint chord conservatively bounds the
// flattening error. The cross product is the perpendicular distance scaled by the chord length;
// comparing squared values avoids a square root.
const Vec2d chord = curve.back() - curve.front();
const double chord_length_sq = chord.squaredNorm();
const double max_cross_sq = deviation * deviation * chord_length_sq;
for (size_t i = 1; i + 1 < curve.size(); ++i) {
const Vec2d offset = curve[i] - curve.front();
const double cross = chord.x() * offset.y() - chord.y() * offset.x();
if (cross * cross > max_cross_sq)
return false;
}
return true;
}
static void subdivide_bezier(const QuinticBezier &curve, QuinticBezier &left, QuinticBezier &right)
{
// Split the curve at t = 0.5 using de Casteljau's algorithm. Each averaging level contributes one
// control point to the left half and one to the right half; the latter is filled backwards to keep
// both resulting control polygons in their original parameter direction.
QuinticBezier subdivision = curve;
left.front() = subdivision.front();
right.back() = subdivision.back();
for (size_t level = 1; level < curve.size(); ++level) {
for (size_t i = 0; i + level < curve.size(); ++i)
subdivision[i] = 0.5 * (subdivision[i] + subdivision[i + 1]);
left[level] = subdivision.front();
right[curve.size() - level - 1] = subdivision[curve.size() - level - 1];
}
}
static void flatten_bezier(const QuinticBezier &curve, const double deviation, std::vector<Vec2d> &output)
{
// Subdivide to at least depth 1 so a rounded corner cannot collapse to a single diagonal chord.
// A uniform subdivision depth keeps samples at equal parameter intervals t = k / 2^depth,
// avoiding abrupt segment-length jumps at adaptive-depth boundaries.
static constexpr size_t max_depth = 16;
std::vector<QuinticBezier> subcurves(2);
subdivide_bezier(curve, subcurves[0], subcurves[1]);
for (size_t depth = 1; depth < max_depth; ++depth) {
bool all_flat = true;
for (const QuinticBezier &c : subcurves)
if (!is_bezier_flat(c, deviation)) {
all_flat = false;
break;
}
if (all_flat)
break;
std::vector<QuinticBezier> finer(subcurves.size() * 2);
for (size_t i = 0; i < subcurves.size(); ++i)
subdivide_bezier(subcurves[i], finer[i * 2], finer[i * 2 + 1]);
subcurves = std::move(finer);
}
// The curve start is deliberately omitted so consecutive curve pieces can share it without duplication.
output.reserve(output.size() + subcurves.size());
for (const QuinticBezier &c : subcurves)
output.emplace_back(c.back());
}
template<typename Output>
static void generate_smooth_hilbert_curve(
coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
const double corner_distance, Output &output)
{
// A Hilbert curve is defined on a square grid whose side is a power of two. As in the unsmoothed
// generator, expand the larger requested dimension to the next valid Hilbert grid size. The output
// clipper or the later region intersection removes the padded part of the traversal.
size_t sz = 2;
const size_t sz0 = std::max(max_x + 1 - min_x, max_y + 1 - min_y);
while (sz < sz0)
sz <<= 1;
const size_t point_count = sz * sz;
output.reserve(point_count);
// The caller normalizes resolution to the unit Hilbert grid; retain a finite positive tolerance
// if this helper is invoked with an invalid resolution.
const double deviation = resolution > 0. && std::isfinite(resolution) ? resolution : EPSILON;
// Construct one canonical 90-degree corner from (-corner_distance, 0) to (0, corner_distance).
// At each end, the first three control points are collinear and equally spaced: the tangent follows
// the adjoining straight leg and the second derivative is zero. The endpoint curvature is therefore
// zero, giving G2 joins to both legs. Every Hilbert turn is an oriented copy of this curve, so flatten
// it only once to the requested chordal-deviation tolerance.
const QuinticBezier corner_curve {{
{-corner_distance, 0.}, {-0.7 * corner_distance, 0.}, {-0.4 * corner_distance, 0.},
{0., 0.4 * corner_distance}, {0., 0.7 * corner_distance}, {0., corner_distance}
}};
std::vector<Vec2d> curve_coefficients;
flatten_bezier(corner_curve, deviation, curve_coefficients);
auto translated_point = [min_x, min_y](size_t idx) {
Point p = hilbert_n_to_xy(idx);
return Point(p.x() + min_x, p.y() + min_y);
};
auto to_vec2d = [](const Point &p) { return Vec2d(double(p.x()), double(p.y())); };
bool has_last_output = false;
Vec2d last_output;
// Fully smoothed adjacent corners may meet at the same segment midpoint. Suppress such duplicates
// to avoid emitting zero-length extrusion segments.
auto add_point = [&output, &has_last_output, &last_output](const Vec2d &point) {
if (!has_last_output || point.x() != last_output.x() || point.y() != last_output.y()) {
output.add_point(point);
last_output = point;
has_last_output = true;
}
};
Vec2d previous = to_vec2d(translated_point(0));
Vec2d corner = to_vec2d(translated_point(1));
add_point(previous);
// Replace each non-collinear Hilbert vertex by the canonical curve expressed in the local basis of
// its incoming and outgoing unit vectors. Collinear vertices remain part of the straight polyline.
for (size_t i = 1; i + 1 < point_count; ++i) {
const Vec2d next = to_vec2d(translated_point(i + 1));
const Vec2d incoming = (corner - previous).normalized();
const Vec2d outgoing = (next - corner).normalized();
const double cross = incoming.x() * outgoing.y() - incoming.y() * outgoing.x();
if (std::abs(cross) < EPSILON) {
add_point(corner);
} else {
add_point(corner - corner_distance * incoming);
for (const Vec2d &coefficient : curve_coefficients)
add_point(corner + coefficient.x() * incoming + coefficient.y() * outgoing);
}
previous = corner;
corner = next;
}
add_point(corner);
}
void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output)
{
if (output.clips())
@@ -296,6 +437,24 @@ void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coo
generate_hilbert_curve(min_x, min_y, max_x, max_y, output);
}
void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
const FillParams &params, InfillPolylineOutput &output)
{
const double smooth_factor = std::isfinite(params.smooth_factor) ?
std::clamp(params.smooth_factor, 0., 1.) : 0.;
if (smooth_factor == 0.) {
this->generate(min_x, min_y, max_x, max_y, resolution, output);
return;
}
const double corner_distance = 0.5 * smooth_factor;
if (output.clips())
generate_smooth_hilbert_curve(
min_x, min_y, max_x, max_y, resolution, corner_distance, static_cast<InfillPolylineClipper&>(output));
else
generate_smooth_hilbert_curve(min_x, min_y, max_x, max_y, resolution, corner_distance, output);
}
template<typename Output>
static void generate_octagram_spiral(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, Output &output)
{
+7
View File
@@ -53,6 +53,11 @@ protected:
friend class InfillPolylineClipper;
virtual void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) = 0;
virtual void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
const FillParams & /* params */, InfillPolylineOutput &output)
{
this->generate(min_x, min_y, max_x, max_y, resolution, output);
}
};
class FillArchimedeanChords : public FillPlanePath
@@ -75,6 +80,8 @@ public:
protected:
bool centered() const override { return false; }
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override;
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
const FillParams &params, InfillPolylineOutput &output) override;
};
class FillOctagramSpiral : public FillPlanePath
+20 -17
View File
@@ -2917,6 +2917,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled,
print.get_layered_nozzle_group_result());
const bool is_bbl_printers = print.is_BBL_printer();
const bool skip_config_block = print.config().gcode_skip_config_block;
const WipeTowerType wipe_tower_type = print.wipe_tower_type();
m_calib_config.clear();
// resets analyzer's tracking data
@@ -3092,7 +3093,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
// as configuration key / value pairs to be parsable by older versions of
// PrusaSlicer G-code viewer.
{
if (is_bbl_printers) {
if (is_bbl_printers && !skip_config_block) {
file.write("; CONFIG_BLOCK_START\n");
std::string full_config;
append_full_config(print, full_config);
@@ -4119,23 +4120,25 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder)
.c_str());
file.write("\n");
file.write("; CONFIG_BLOCK_START\n");
std::string full_config;
append_full_config(print, full_config);
if (!full_config.empty())
file.write(full_config);
if (!skip_config_block) {
file.write("; CONFIG_BLOCK_START\n");
std::string full_config;
append_full_config(print, full_config);
if (!full_config.empty())
file.write(full_config);
// SoftFever: write compatiple info
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature);
file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str());
file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0));
file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value);
//SF TODO
// file.write_format("; variable_layer_height = %d\n", print.ad.adaptive_layer_height ? 1 : 0);
file.write("; CONFIG_BLOCK_END\n\n");
// SoftFever: write compatiple info
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature);
file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str());
file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0));
file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value);
//SF TODO
// file.write_format("; variable_layer_height = %d\n", print.ad.adaptive_layer_height ? 1 : 0);
file.write("; CONFIG_BLOCK_END\n\n");
} // !skip_config_block
}
file.write("\n");
+1 -1
View File
@@ -185,7 +185,7 @@ struct LayerResult {
// It is used for the pressure equalizer because it needs to buffer one layer back.
bool nop_layer_result { false };
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; }
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<size_t>::max(), false, false, true}; }
};
class GCode {
+6
View File
@@ -5926,8 +5926,11 @@ void GCodeProcessor::process_G10(const GCodeReader::GCodeLine& line)
GCodeReader::GCodeLine g10;
g10.set(Axis::E, -this->m_parser.config().retraction_length.get_at(m_extruder_id));
g10.set(Axis::F, this->m_parser.config().retraction_speed.get_at(m_extruder_id) * 60);
//Orca: Firmware retract emulation must not change the modal G1 feedrate.
const float feedrate = m_feedrate;
--m_g1_line_id;
process_G1(g10);
m_feedrate = feedrate;
}
void GCodeProcessor::process_G11(const GCodeReader::GCodeLine& line)
@@ -5936,8 +5939,11 @@ void GCodeProcessor::process_G11(const GCodeReader::GCodeLine& line)
GCodeReader::GCodeLine g11;
g11.set(Axis::E, this->m_parser.config().retraction_length.get_at(m_extruder_id) + this->m_parser.config().retract_restart_extra.get_at(m_extruder_id));
g11.set(Axis::F, this->m_parser.config().deretraction_speed.get_at(m_extruder_id) * 60);
// Orca: Firmware unretract emulation must not change the modal G1 feedrate.
const float feedrate = m_feedrate;
--m_g1_line_id;
process_G1(g11);
m_feedrate = feedrate;
}
void GCodeProcessor::process_G20(const GCodeReader::GCodeLine& line)
+28 -24
View File
@@ -3,6 +3,7 @@
#include "I18N.hpp"
#include "PrintConfig.hpp"
#include "ClipperUtils.hpp"
#include "Geometry/ArcWelder.hpp"
#include "Line.hpp"
#include <algorithm>
#include <iomanip>
@@ -1018,45 +1019,48 @@ std::string GCodeWriter::_spiral_travel_to_z(double z, const Vec2d &ij_offset, c
}
if (!this->config.enable_arc_fitting) { // Orca: if arc fitting is disabled, approximate the arc with small linear segments
std::ostringstream oss;
const double z_start = m_pos(2); // starting Z height
// --------------------------------------------------------------------
// Determine number of segments based on Resolution
// --------------------------------------------------------------------
const double ref_resolution = 0.01; // reference resolution in mm
const double ref_segments = 8.0; // reference number of segments at reference resolution
// number of linear segments to use for approximating the arc, clamp between 4 and 16
const int segments = std::clamp(int(std::round(ref_segments * (ref_resolution / m_resolution))), 4, 16);
// --------------------------------------------------------------------
const double px = m_pos(0) - m_x_offset; // take plate offset into consideration
const double py = m_pos(1) - m_y_offset; // take plate offset into consideration
const double cx = px + ij_offset(0); // center x
const double cy = py + ij_offset(1); // center y
const double radius = ij_offset.norm(); // radius
// Number of linear segments approximating the circle, chosen so that a chord never deviates
// from the true arc by more than the slicing resolution. A resolution of 0 means "no
// simplification", which has no finite segment count, so it takes the upper bound.
constexpr size_t min_segments = 8; // keep a small spiral visibly round
constexpr size_t max_segments = 128; // bound the emitted G-code
const int segments = int(m_resolution > 0. ?
std::clamp(Geometry::ArcWelder::arc_discretization_steps(radius, 2. * M_PI, m_resolution), min_segments, max_segments) :
max_segments);
const double a0 = std::atan2(py - cy, px - cx); // start angle
const double delta = 2.0 * M_PI; // CCW full circle
if (full_gcode_comment)
oss << ";" << comment << "\n";
auto emit_point = [&output](const Vec3d &point) {
GCodeG1Formatter w;
w.emit_xyz(point);
output += w.string();
};
oss << "G1 F" << (speed * 60.0) << "\n"; // set feedrate
output.reserve(size_t(segments) * 40); // ~40 characters per emitted G1 line
GCodeG1Formatter w; // set feedrate
w.emit_f(speed * 60.0);
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
output += w.string();
// approximate the arc with small linear segments (without the last point which is added later to ensure exactness)
for (int i = 1; i < segments; ++i) {
double t = double(i) / segments; // parametric position along arc
double a = a0 + delta * t; // CCW arc param
double x = cx + radius * std::cos(a); // point on circle
double y = cy + radius * std::sin(a); // point on circle
double zz = z_start + (z - z_start) * t; // interpolated Z height
oss << "G1 X" << x << " Y" << y << " Z" << zz << "\n";
const double t = double(i) / segments; // parametric position along arc
const double a = a0 + 2. * M_PI * t; // CCW arc param, full circle
emit_point(Vec3d(cx + radius * std::cos(a), // point on circle
cy + radius * std::sin(a),
z_start + (z - z_start) * t)); // interpolated Z height
}
oss << "G1 X" << px << " Y" << py << " Z" << z << "\n"; // final point to ensure exactness
output = oss.str();
emit_point(Vec3d(px, py, z)); // final point to ensure exactness
} else { // Orca: if arc fitting is enabled emit a G2/G3 command for the spiral lift
output = std::string("G17") + (full_gcode_comment ? " ; XY plane for arc\n" : "\n");
+17 -12
View File
@@ -1977,7 +1977,7 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
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));
ExPolygons unsupported_filtered = opening_ex(unsupported, perimeter_spacing);
if (!unsupported_filtered.empty()) {
//to_draw.insert(to_draw.end(), last.begin(), last.end());
@@ -2090,35 +2090,40 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
//TODO: add other polys as holes inside this one (-margin)
} else { // if(this->config->counterbore_hole_bridging.value == chbBridges)
// Orca: Partial counterbore bridging is mask-based. Preserve the supported
// remainder (`last`) and use simplified BridgeDetector coverage to derive the
// remainder and use simplified BridgeDetector coverage to derive the
// bridgeable counterbore span. The span is grown from supported material,
// shrunk back, stripped from `last`, and expanded back. It is then prevented
// from intruding deeper into `last` than the explicit anchor overlap.
// Finally, add the allowed anchor band from `last` then remove the
// shrunk back, stripped from the remaining normal surface, and expanded back.
// It is then prevented from intruding deeper into it than the explicit anchor overlap.
// Finally, add the allowed anchor band from it then remove the
// narrow hole-side wall contact, which must remain unbridgeable.
last = diff_ex(last, unsupported_filtered, ApplySafetyOffset::Yes);
const ExPolygons remaining = diff_ex(last, unsupported_filtered, ApplySafetyOffset::Yes);
ExPolygons bridgeable_filtered;
for (ExPolygon& poly : bridgeable) {
poly.simplify(perimeter_spacing, &bridgeable_filtered);
}
bridgeable_filtered = opening_ex(bridgeable_filtered, ext_perimeter_width);
// Get rid of coarseness of the resulted bridgeable area by using the original supported area as reference.
// This is to avoid keeping tiny bridgeable areas that are far from the supported area, or protrude into it.
bridgeable_filtered = union_ex(offset_ex(last, perimeter_spacing), bridgeable_filtered);
// This is to avoid keeping tiny bridgeable areas that are far from the supported area, or protrude into it.
bridgeable_filtered = union_ex(offset_ex(remaining, perimeter_spacing), bridgeable_filtered);
bridgeable_filtered = offset_ex(bridgeable_filtered, -perimeter_spacing);
bridgeable_filtered = diff_ex(bridgeable_filtered, last, ApplySafetyOffset::Yes);
bridgeable_filtered = diff_ex(bridgeable_filtered, remaining, ApplySafetyOffset::Yes);
bridgeable_filtered = opening_ex(bridgeable_filtered, perimeter_spacing); // filter noise from the diff_ex
bridgeable_filtered = offset_ex(bridgeable_filtered, perimeter_spacing); // restore the size to the original bridgeable area
// Safety measure: Keep the bridge mask from intruding deeper into the
// supported anchor region (`last`) than the explicit anchor overlap.
bridgeable_filtered = diff_ex(bridgeable_filtered, offset_ex(last, -bridge_anchor_offset));
// supported anchor region than the explicit anchor overlap.
bridgeable_filtered = diff_ex(bridgeable_filtered, offset_ex(remaining, -bridge_anchor_offset));
ExPolygons bridge_anchor_areas = intersection_ex(last, offset_ex(unsupported_filtered, bridge_anchor_offset));
ExPolygons bridge_anchor_areas = intersection_ex(remaining, offset_ex(unsupported_filtered, bridge_anchor_offset));
unsupported_filtered = union_ex(bridgeable_filtered, bridge_anchor_areas); // add bridge anchor
unsupported_filtered = opening_ex(unsupported_filtered, bridge_anchor_offset); // remove anchor area from hole-side walls, it must remain unbridgeable
// update 'last' only if we have a valid bridgeable area, otherwise we will lose the original unsupported area
if (!unsupported_filtered.empty())
last = remaining;
// TODO: Fix the case with thin outer walls around the bridge (1~2 walls) where classic wall
// might generate two walls in a tiny space or non at all if "Detect thin walls" is not activated
}
+2
View File
@@ -1791,6 +1791,8 @@ namespace client
// from UTF8 to UTF16 don't bail out.
msg += boost::nowide::narrow(boost::nowide::widen(error_line));
msg += '\n';
// The error dialog (MsgDialog.cpp) renders this excerpt monospaced. It recognizes a source
// line directly above a caret line of spaces and a single '^'.
for (size_t i = 0; i < error_pos; ++ i)
msg += ' ';
msg += "^\n";
+7 -4
View File
@@ -1037,6 +1037,7 @@ static std::vector<std::string> s_Preset_print_options{
"fill_multiline",
"gyroid_optimized",
"sparse_infill_pattern",
"sparse_infill_smooth_factor",
"lateral_lattice_angle_1",
"lateral_lattice_angle_2",
"infill_overhang_angle",
@@ -1090,7 +1091,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_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",
"brim_width", "brim_object_gap", "brim_flow_ratio", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "brim_ears_outer_only", "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
@@ -1405,7 +1406,7 @@ static std::vector<std::string> s_Preset_machine_limits_options {
static std::vector<std::string> s_Preset_printer_options {
"printer_technology",
"printable_area", "extruder_printable_area", "support_parallel_printheads", "parallel_printheads_count", "parallel_printheads_bed_exclude_areas", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor",
"fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs",
"gcode_skip_config_block", "fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs",
"single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode",
"printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type",
"printable_height", "extruder_printable_height", "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod",
@@ -3785,12 +3786,14 @@ void PresetCollection::update_library_profile_excluded_from()
}
// Check all presets that has the same alias as the filament presets with empty compatible_printers in Orca Filament Library.
// A printer specific profile supersedes the generic one, no matter whether it lives in a vendor bundle or in the
// library itself.
for (const Preset& preset : m_presets) {
if (preset.vendor == nullptr || preset.vendor->name == PresetBundle::ORCA_FILAMENT_LIBRARY)
if (preset.vendor == nullptr)
continue;
const auto* compatible_printers = dynamic_cast<const ConfigOptionStrings*>(preset.config.option("compatible_printers"));
// All profiles in concrete vendor profile shouldn't have empty compatible_printers, but here we check it for safety.
// Profiles with empty compatible_printers are the generic ones, they never supersede anything.
if (compatible_printers == nullptr || compatible_printers->values.empty())
continue;
auto itr = excluded_froms.find(preset.alias);
+29 -1
View File
@@ -1939,6 +1939,13 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(1));
def = this->add("brim_ears_outer_only", coBool);
def->label = L("Brim ears outer only");
def->category = L("Support");
def->tooltip = L("Generate mouse ears only on the outer contour of the model, excluding holes and enclosed sections.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("compatible_printers", coStrings);
def->label = L("Select printers");
def->mode = comAdvanced;
@@ -3459,6 +3466,18 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.push_back(L("Octagram Spiral"));
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipCrossHatch));
def = this->add("sparse_infill_smooth_factor", coPercent);
def->label = L("Sparse infill smooth factor");
def->category = L("Strength");
def->tooltip = L("Controls how strongly sparse infill corners are rounded. 0% keeps the original right-angle path, "
"while 100% produces the largest possible curves between adjacent infill lines. "
"Currently applies only to the Hilbert Curve.");
def->sidetext = "%";
def->min = 0;
def->max = 100;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionPercent(0));
def = this->add("top_surface_acceleration", coFloats);
def->label = L("Top surface");
def->category = L("Speed");
@@ -4242,6 +4261,15 @@ void PrintConfigDef::init_fff_params()
def->readonly = false;
def->set_default_value(new ConfigOptionEnum<GCodeFlavor>(gcfMarlinLegacy));
def = this->add("gcode_skip_config_block", coBool);
def->label = L("Skip G-code config block");
def->tooltip = L("Do not write the CONFIG_BLOCK (slicer configuration key/value pairs) into the G-code file. "
"This can help with printers whose firmware crashes when parsing these comment lines "
"(e.g. Anycubic go-klipper). Note: the G-code file will no longer contain slicer settings, "
"so importing it back into OrcaSlicer will not restore the configuration.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("pellet_modded_printer", coBool);
def->label = L("Pellet Modded Printer");
def->tooltip = L("Enable this option if your printer uses pellets instead of filaments.");
@@ -4275,7 +4303,7 @@ void PrintConfigDef::init_fff_params()
"slow down.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(0));
//BBS
def = this->add("infill_combination", coBool);
def->label = L("Infill combination");
+3 -1
View File
@@ -1082,6 +1082,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloat, brim_width))
((ConfigOptionFloat, brim_ears_detection_length))
((ConfigOptionFloat, brim_ears_max_angle))
((ConfigOptionBool, brim_ears_outer_only))
((ConfigOptionFloat, skirt_start_angle))
((ConfigOptionBool, bridge_no_support))
((ConfigOptionFloat, elefant_foot_compensation))
@@ -1264,6 +1265,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionString, sparse_infill_rotate_template))
((ConfigOptionPercent, sparse_infill_density))
((ConfigOptionEnum<InfillPattern>, sparse_infill_pattern))
((ConfigOptionPercent, sparse_infill_smooth_factor))
((ConfigOptionFloat, lateral_lattice_angle_1))
((ConfigOptionFloat, lateral_lattice_angle_2))
((ConfigOptionFloat, infill_overhang_angle))
@@ -1545,7 +1547,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionBool, gcode_add_line_number))
((ConfigOptionBool, bbl_bed_temperature_gcode))
((ConfigOptionEnum<GCodeFlavor>, gcode_flavor))
((ConfigOptionBool, gcode_skip_config_block))
((ConfigOptionFloat, time_cost))
((ConfigOptionString, layer_change_gcode))
((ConfigOptionString, time_lapse_gcode))
+2
View File
@@ -1175,6 +1175,7 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "brim_type"
|| opt_key == "brim_ears_max_angle"
|| opt_key == "brim_ears_detection_length"
|| opt_key == "brim_ears_outer_only"
// BBS: brim generation depends on printing speed
|| opt_key == "outer_wall_speed"
|| opt_key == "small_perimeter_speed"
@@ -1409,6 +1410,7 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "infill_overhang_angle") {
steps.emplace_back(posInfill);
} else if (opt_key == "sparse_infill_pattern"
|| opt_key == "sparse_infill_smooth_factor"
|| opt_key == "symmetric_infill_y_axis"
|| opt_key == "infill_shift_step"
|| opt_key == "sparse_infill_rotate_template"
-5
View File
@@ -51,9 +51,4 @@
// Enable extension of tool position imgui dialog to show actual speed profile
#define ENABLE_ACTUAL_SPEED_DEBUG 1
// Disable layout inspector for public release
#if BBL_RELEASE_TO_PUBLIC
#define WXINSPECTOR_DISABLE
#endif
#endif // _prusaslicer_technologies_h_
+64 -14
View File
@@ -70,6 +70,9 @@ float FullTransparentModdifiedToFixAlpha = 0.3f;
// value like 0.18f could not because in C++ (int)(0.18f * 255) == 45 however in OpenGL it renders this as 46
// which breaks the `SelectMachineDialog::record_edge_pixels_data()` function!
float FULL_BLACK_THRESHOLD = 0.2f;
// Keep depth_tex away from texture unit 0 to avoid sampler-type aliasing with
// shadow/environment samplers when realistic view is disabled.
static constexpr int OUTLINE_DEPTH_TEX_UNIT = 5;
Slic3r::ColorRGBA adjust_color_for_rendering(const Slic3r::ColorRGBA &colors)
{
@@ -518,6 +521,37 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
glsafe(::glStencilMask(0xFF));
glsafe(::glDisable(GL_STENCIL_TEST));
// render the outline using depth buffer and discard the pixels that are not on the outline
// The silhouette is resolved per sample in the shader (see DetectSilho in gouraud.fs/phong.fs).
// That needs the GL 3.2 entry points and a shader that declares depth_tex as sampler2DMS, which
// only the 140 ones do and only under GL_ARB_texture_multisample - so ask the compiled program
// rather than the GL version, or a sampler2D ends up bound to a multisample texture.
// Only the Arb branch below allocates a multisample texture, so keep the target consistent with it.
const bool use_msaa_outline = framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb &&
GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 2) &&
shader->get_uniform_location("msaa_samples") >= 0;
const GLenum depth_tex_target = use_msaa_outline ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
// Keep the depth texture off image unit 0. The object shaders leave shadow_map (and
// environment_tex) at the default sampler value 0 whenever the shadow pass is skipped - which is
// the case with realistic view off - and GL forbids two sampler types referring to the same image
// unit. A sampler2DMS on unit 0 then makes every draw fail with INVALID_OPERATION on drivers that
// enforce it (Mesa), i.e. the model disappears entirely. Unit 5 is unused (shadow_map takes 4).
const int depth_tex_unit = OUTLINE_DEPTH_TEX_UNIT;
int aa_samples = 1;
if (use_msaa_outline) {
if (const AppConfig* app_config = GUI::wxGetApp().app_config; app_config != nullptr) {
const std::string value = app_config->get(SETTING_OPENGL_AA_SAMPLES);
if (value == "2" || value == "4" || value == "8" || value == "16")
aa_samples = ::atoi(value.c_str());
}
// Never request more samples than the driver supports for depth textures (a 1-sample texture
// is used when MSAA is disabled, keeping a single code path for the sampler2DMS shader).
GLint max_samples = 1;
glsafe(::glGetIntegerv(GL_MAX_DEPTH_TEXTURE_SAMPLES, &max_samples));
if (aa_samples > max_samples)
aa_samples = max_samples < 1 ? 1 : max_samples;
if (aa_samples < 1)
aa_samples = 1;
}
// 1st. render pass, render the model into a separate render target that has only depth buffer
GLuint depth_fbo = 0;
GLuint depth_tex = 0;
@@ -525,21 +559,26 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
glsafe(::glGenFramebuffers(1, &depth_fbo));
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, depth_fbo));
glActiveTexture(GL_TEXTURE0);
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
glsafe(::glGenTextures(1, &depth_tex));
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
glsafe(::glBindTexture(depth_tex_target, depth_tex));
if (use_msaa_outline) {
// Multisample textures do not take filter/wrap parameters.
glsafe(::glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, aa_samples, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), GL_TRUE));
} else {
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
}
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_tex, 0));
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depth_tex_target, depth_tex, 0));
} else {
glsafe(::glGenFramebuffersEXT(1, &depth_fbo));
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, depth_fbo));
glActiveTexture(GL_TEXTURE0);
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
glsafe(::glGenTextures(1, &depth_tex));
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
@@ -550,12 +589,15 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depth_tex, 0));
}
// Unbind before drawing: the texture is this framebuffer's depth attachment, so leaving it bound
// to a sampled unit would be a feedback loop.
glsafe(::glBindTexture(depth_tex_target, 0));
glsafe(::glActiveTexture(GL_TEXTURE0));
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
model.render(shader);
else
model.render(this->tverts_range, shader);
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
// 2nd. render pass, just a normal render with the depth buffer passed as a texture
if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb) {
@@ -565,13 +607,17 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
}
shader->set_uniform("is_outline", true);
shader->set_uniform("screen_size", Vec2f{cnv_size.get_width(), cnv_size.get_height()});
glActiveTexture(GL_TEXTURE0);
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
shader->set_uniform("depth_tex", 0);
shader->set_uniform("msaa_samples", aa_samples);
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
glsafe(::glBindTexture(depth_tex_target, depth_tex));
glsafe(::glActiveTexture(GL_TEXTURE0));
shader->set_uniform("depth_tex", depth_tex_unit);
simple_render(shader, model_objects, colors);
// Some clean up to do
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
glsafe(::glBindTexture(depth_tex_target, 0));
glsafe(::glActiveTexture(GL_TEXTURE0));
shader->set_uniform("is_outline", false);
if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb) {
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0));
@@ -1075,6 +1121,10 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
const float support_normal_z = get_selection_support_normal_z();
// Prime depth_tex on every frame so non-outline draws do not keep the
// default sampler unit 0, which can conflict with other sampler types.
shader->set_uniform("depth_tex", OUTLINE_DEPTH_TEX_UNIT);
for (GLVolumeWithIdAndZ& volume : to_render) {
#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
if (type == ERenderType::Transparent) {
+17 -5
View File
@@ -70,6 +70,12 @@ void ConfigManipulation::toggle_line(const std::string& opt_key, const bool togg
cb_toggle_line(opt_key, toggle, opt_index);
}
void ConfigManipulation::set_option_label(const std::string& opt_key, const wxString& label, int opt_index)
{
if (cb_set_option_label)
cb_set_option_label(opt_key, label, opt_index);
}
void ConfigManipulation::check_nozzle_recommended_temperature_range(DynamicPrintConfig *config) {
if (is_msg_dlg_already_exist)
return;
@@ -707,6 +713,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
bool has_top_shell = has_top_shell_layers && config->option<ConfigOptionPercent>("top_surface_density")->value > 0;
bool has_bottom_shell = config->opt_int("bottom_shell_layers") > 0;
bool has_solid_infill = has_top_shell_layers || has_bottom_shell;
toggle_line("sparse_infill_smooth_factor", pattern == ipHilbertCurve);
toggle_field("top_surface_pattern", has_top_shell);
toggle_field("bottom_surface_pattern", has_bottom_shell);
toggle_field("top_surface_density", has_top_shell_layers);
@@ -807,14 +814,19 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
toggle_field("outer_wall_filament_id", have_perimeters || have_brim);
toggle_field("inner_wall_filament_id", have_perimeters || have_brim);
bool have_brim_ear = (config->opt_enum<BrimType>("brim_type") == btEar);
const BrimType brim_type = config->opt_enum<BrimType>("brim_type");
const bool have_auto_brim_ear = brim_type == btEar;
const bool have_painted_brim_ear = brim_type == btPainted;
set_option_label("brim_width", have_auto_brim_ear ? _L("Brim ear radius") : _L("Brim width"));
const auto brim_width = config->opt_float("brim_width");
// disable brim_ears_max_angle and brim_ears_detection_length if brim_width is 0
// Automatic brim ear settings require a non-zero brim width.
toggle_field("brim_ears_max_angle", brim_width > 0.0f);
toggle_field("brim_ears_detection_length", brim_width > 0.0f);
// hide brim_ears_max_angle and brim_ears_detection_length if brim_ear is not selected
toggle_line("brim_ears_max_angle", have_brim_ear);
toggle_line("brim_ears_detection_length", have_brim_ear);
// Painted ears carry their own radius and do not depend on brim_width.
toggle_field("brim_ears_outer_only", have_painted_brim_ear || brim_width > 0.0f);
toggle_line("brim_ears_max_angle", have_auto_brim_ear);
toggle_line("brim_ears_detection_length", have_auto_brim_ear);
toggle_line("brim_ears_outer_only", have_auto_brim_ear || have_painted_brim_ear);
// Hide Elephant foot compensation layers if elefant_foot_compensation is not enabled
toggle_line("elefant_foot_compensation_layers", config->opt_float("elefant_foot_compensation") > 0 || config->option<ConfigOptionPercent>("elefant_foot_layers_density")->get_abs_value(1.0f) < 1.0f);
+6 -1
View File
@@ -29,6 +29,7 @@ class ConfigManipulation
std::function<void()> load_config = nullptr;
std::function<void (const std::string&, bool toggle, int opt_index)> cb_toggle_field = nullptr;
std::function<void(const std::string &, bool toggle, int opt_index)> cb_toggle_line = nullptr;
std::function<void(const std::string &, const wxString &, int opt_index)> cb_set_option_label = nullptr;
// callback to propagation of changed value, if needed
std::function<void(const std::string&, const boost::any&)> cb_value_change = nullptr;
//BBS: change local config to const DynamicPrintConfig
@@ -45,10 +46,12 @@ public:
std::function<void(const std::string&, const boost::any&)> cb_value_change,
//BBS: change local config to DynamicPrintConfig
const DynamicPrintConfig* local_config = nullptr,
wxWindow* msg_dlg_parent = nullptr) :
wxWindow* msg_dlg_parent = nullptr,
std::function<void(const std::string &, const wxString &, int opt_index)> cb_set_option_label = nullptr) :
load_config(load_config),
cb_toggle_field(cb_toggle_field),
cb_toggle_line(cb_toggle_line),
cb_set_option_label(cb_set_option_label),
cb_value_change(cb_value_change),
m_msg_dlg_parent(msg_dlg_parent),
local_config(local_config) {}
@@ -58,6 +61,7 @@ public:
load_config = nullptr;
cb_toggle_field = nullptr;
cb_toggle_line = nullptr;
cb_set_option_label = nullptr;
cb_value_change = nullptr;
}
@@ -67,6 +71,7 @@ public:
t_config_option_keys const &applying_keys() const;
void toggle_field(const std::string& field_key, const bool toggle, int opt_index = -1);
void toggle_line(const std::string& field_key, const bool toggle, int opt_index = -1);
void set_option_label(const std::string& field_key, const wxString& label, int opt_index = -1);
// FFF print
void update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config = false, const bool is_plate_config = false);
+96 -6
View File
@@ -1842,6 +1842,10 @@ void GLCanvas3D::enable_separator_toolbar(bool enable)
m_separator_toolbar.set_enabled(enable);
}
bool GLCanvas3D::has_mouse_capture() const {
return m_canvas != nullptr && m_canvas->HasCapture();
}
void GLCanvas3D::zoom_to_bed()
{
BoundingBoxf3 box = m_bed.build_volume().bounding_volume();
@@ -2182,7 +2186,7 @@ void GLCanvas3D::render(bool only_init)
// Negative coordinate means out of the window, likely because the window was deactivated.
// In that case the tooltip should be hidden.
if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) {
if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0. || has_mouse_capture()) { // ORCA continue to capture mouse pos mid drag
if (tooltip.empty())
tooltip = m_layers_editing.get_tooltip(*this);
@@ -4170,6 +4174,23 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
// BBS: single snapshot
Plater::SingleSnapshot single(wxGetApp().plater());
#ifdef __WXMAC__
// On macOS, the mouse key state is only present for mouse btn related events such as wxEVT_LEFT_DOWN.
// For other events, all buttons are reported as non-pressed, such as window leaving event. This causes
// imgui stopped responding if cursor moved out of window, such as
// https://github.com/OrcaSlicer/OrcaSlicer/pull/14999#issuecomment-5151344759
// We solve this by correcting the state of the event from the actual mouse state querying with `wxGetMouseState()`
// so it works like on other platforms.
{
const auto state = wxGetMouseState();
evt.SetLeftDown(state.LeftIsDown());
evt.SetMiddleDown(state.MiddleIsDown());
evt.SetRightDown(state.RightIsDown());
evt.SetAux1Down(state.Aux1IsDown());
evt.SetAux2Down(state.Aux2IsDown());
}
#endif
#if ENABLE_RETINA_GL
const float scale = m_retina_helper->get_scale_factor();
evt.SetX(evt.GetX() * scale);
@@ -4183,11 +4204,27 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
// ignore left up events coming from imgui windows and not processed by them
m_mouse.ignore_left_up = true;
m_tooltip.set_in_imgui(false);
if (imgui->update_mouse_data(evt)) {
// while a non-ImGui drag is already in progress (gizmo grabber, object move, rectangle selection, layer editing),
// don't let ImGui/ImGuizmo claim the event just because the cursor is hovering something like the navigator cube
// that incorrectly suppresses the active drag's tooltip and can interrupt its processing. The active drag always takes priority.
const bool other_drag_active = m_gizmos.is_dragging() || m_mouse.dragging || m_rectangle_selection.is_dragging() || m_layers_editing.state == LayersEditing::Editing;
if (imgui->update_mouse_data(evt) && !other_drag_active) {
if ((evt.LeftDown() || (evt.Moving() && (evt.AltDown() || evt.ShiftDown()))) && m_canvas != nullptr)
m_canvas->SetFocus();
m_mouse.position = evt.Leaving() ? Vec2d(-1.0, -1.0) : pos.cast<double>();
m_tooltip.set_in_imgui(true);
// ORCA keep tracking mouse position while drag active and cursor not in window bounds
const bool imgui_dragging_active = (GImGui != nullptr && ImGui::GetIO().MouseDown[0] && GImGui->ActiveId != 0) || m_navigator_dragging;
if (!has_mouse_capture() && imgui_dragging_active)
m_canvas->CaptureMouse();
// release capture as soon as the button goes up
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
mouse_up_cleanup();
render();
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
@@ -4284,6 +4321,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_main_toolbar.on_mouse(evt2, *this);
}
// ORCA keep tracking mouse position while drag active and cursor not in window bounds
if (!has_mouse_capture() && evt.LeftIsDown() && m_gizmos.is_dragging())
m_canvas->CaptureMouse();
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
mouse_up_cleanup();
@@ -4389,6 +4430,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
// Start editing the layer height.
m_layers_editing.state = LayersEditing::Editing;
_perform_layer_editing_action(&evt);
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
m_canvas->CaptureMouse();
}
else {
@@ -4402,6 +4446,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
&& m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation
&& m_gizmos.get_current_type() != GLGizmosManager::FuzzySkin) {
m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect);
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
m_canvas->CaptureMouse();
m_dirty = true;
}
}
@@ -4469,6 +4517,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_mouse.drag.start_position_3D = m_mouse.scene_position;
m_sequential_print_clearance_first_displacement = true;
m_moving = true;
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
m_canvas->CaptureMouse();
}
}
}
@@ -4477,6 +4528,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
else if (evt.Dragging() && evt.LeftIsDown() && m_mouse.drag.move_volume_idx != -1 && m_layers_editing.state == LayersEditing::Unknown) {
if (m_canvas_type != ECanvasType::CanvasAssembleView) {
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
m_canvas->CaptureMouse();
if (!m_mouse.drag.move_requires_threshold) {
m_mouse.dragging = true;
Vec3d cur_pos = m_mouse.drag.start_position_3D;
@@ -4528,6 +4583,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
else if (evt.Dragging() && evt.LeftIsDown() && m_picking_enabled && m_rectangle_selection.is_dragging()) {
//BBS not in assemble view
if (m_canvas_type != ECanvasType::CanvasAssembleView) {
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
m_canvas->CaptureMouse();
m_rectangle_selection.dragging(pos.cast<double>());
m_dirty = true;
}
@@ -4537,12 +4596,19 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (m_layers_editing.state != LayersEditing::Unknown && layer_editing_object_idx != -1) {
if (m_layers_editing.state == LayersEditing::Editing) {
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
m_canvas->CaptureMouse();
_perform_layer_editing_action(&evt);
m_mouse.position = pos.cast<double>();
}
}
// do not process the dragging if the left mouse was set down in another canvas
else if (is_camera_rotate(evt, button_mappings)) {
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
m_canvas->CaptureMouse();
// Orca: Sphere rotation for painting view
// if dragging over blank area with left button or other button mapped to rotate, then rotate
bool middle_or_right_button_used_as_rotate = (evt.MiddleIsDown() && button_mappings[MouseButton::Middle] == MouseAction::Rotation) ||
@@ -4622,6 +4688,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0);
}
else if (is_camera_pan(evt, button_mappings)) {
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
m_canvas->CaptureMouse();
// if dragging with right button or if button functions swapped and dragging with left button over blank area then pan
if (m_mouse.is_start_position_2D_defined()) {
// get point in model space at Z = 0
@@ -4686,7 +4756,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
deselect_all();
}
//BBS Select plate in this 3D canvas.
else if (evt.LeftUp() && !m_mouse.dragging && m_picking_enabled && !m_hover_plate_idxs.empty() && (m_canvas_type == CanvasView3D) && !is_layers_editing_enabled())
// The left up may come from an ImGui window (e.g. a drag started on the gizmo floating window and released over the bed),
// in which case it must not be treated as a click on the plate, otherwise the gizmo would be closed (see deselect_all below).
else if (evt.LeftUp() && !m_mouse.ignore_left_up && !m_mouse.dragging && m_picking_enabled && !m_hover_plate_idxs.empty() && (m_canvas_type == CanvasView3D) && !is_layers_editing_enabled())
{
int hover_idx = m_hover_plate_idxs.front();
wxGetApp().plater()->select_plate_by_hover_id(hover_idx);
@@ -6033,9 +6105,18 @@ void GLCanvas3D::_render_3d_navigator()
{
if (!wxGetApp().show_3d_navigator()) {
m_canvas_toolbar_pos[0] = 0;
m_navigator_dragging = false;
return;
}
// Fix stealing capture event from other drag events
const bool other_drag_active = !m_navigator_dragging && (m_moving || m_rectangle_selection.is_dragging() || m_gizmos.is_dragging() || m_layers_editing.state == LayersEditing::Editing);
ImGuiIO& io = ImGui::GetIO();
const bool saved_mouse_down0 = io.MouseDown[0];
if (other_drag_active)
io.MouseDown[0] = false;
ImGuizmo::BeginFrame();
auto& style = ImGuizmo::GetStyle();
@@ -6060,7 +6141,6 @@ void GLCanvas3D::_render_3d_navigator()
sc *= (float) dpi / (float) DPI_DEFAULT;
#endif // WIN32
const ImGuiIO& io = ImGui::GetIO();
const float viewManipulateLeft = 0;
const float viewManipulateTop = io.DisplaySize.y;
const float camDistance = 8.f;
@@ -6084,6 +6164,10 @@ void GLCanvas3D::_render_3d_navigator()
camDistance, ImVec2(viewManipulateLeft, viewManipulateTop - size), ImVec2(size, size),
0x00101010);
// Restore the real mouse-down state
if (other_drag_active)
io.MouseDown[0] = saved_mouse_down0;
if (result.changed) {
for (unsigned int c = 0; c < 4; ++c) {
for (unsigned int r = 0; r < 4; ++r) {
@@ -6115,6 +6199,8 @@ void GLCanvas3D::_render_3d_navigator()
request_extra_frame();
}
m_navigator_dragging = result.dragging;
}
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0
@@ -9226,8 +9312,12 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar()
//ORCA ImGui::IsWindowHovered() returns false when left_down events on buttons that causes scrollbar disappears for a short time
auto win_pos = ImGui::GetWindowPos();
bool is_win_hovered = ImGui::IsMouseHoveringRect(win_pos, win_pos + ImVec2(window_width + (show_scroll ? scrollbar_size : 0), window_height), !show_scroll); // use non clipped rectangle to reserve clickable area for scrollbar track
m_sel_plate_toolbar.is_display_scrollbar = is_win_hovered;
bool is_win_hovered = ImGui::IsMouseHoveringRect(win_pos, win_pos + ImVec2(window_width + (show_scroll ? scrollbar_size : 0), window_height), !show_scroll);
// Also show scrollbar visible and continue to capture mouse position
const bool is_scrollbar_active_drag = GImGui != nullptr && ImGui::GetIO().MouseDown[0] && GImGui->ActiveId != 0 && GImGui->ActiveIdWindow == ImGui::GetCurrentWindow();
m_sel_plate_toolbar.is_display_scrollbar = is_win_hovered || is_scrollbar_active_drag;
imgui.end();
}
+6
View File
@@ -589,6 +589,7 @@ private:
bool m_toolpath_outside{ false };
ECursorType m_cursor_type;
GLSelectionRectangle m_rectangle_selection;
bool m_navigator_dragging{ false };
//BBS:add plate related logic
mutable std::vector<int> m_hover_volume_idxs;
@@ -916,6 +917,7 @@ public:
void update_volumes_colors_by_extruder();
bool is_dragging() const { return m_gizmos.is_dragging() || m_moving; }
bool has_mouse_capture() const;
void render(bool only_init = false);
bool is_rendering_enabled()
@@ -1117,6 +1119,10 @@ public:
void set_mouse_as_dragging() { m_mouse.dragging = true; }
bool is_mouse_dragging() const { return m_mouse.dragging; }
// True when the current left up event comes from an ImGui window and was not processed by it
// (e.g. a drag that started on a gizmo floating window and was released over the 3D scene).
// Such a release is the end of an ImGui interaction, not a click on the scene.
bool is_mouse_left_up_ignored() const { return m_mouse.ignore_left_up; }
double get_size_proportional_to_max_bed_size(double factor) const;
+4 -4
View File
@@ -256,18 +256,18 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
}
}
void show_error(wxWindow* parent, const wxString& message, bool monospaced_font)
void show_error(wxWindow* parent, const wxString& message, bool has_code_excerpts)
{
wxGetApp().CallAfter([=] {
ErrorDialog msg(parent, message, monospaced_font);
ErrorDialog msg(parent, message, has_code_excerpts);
msg.ShowModal();
});
}
void show_error(wxWindow* parent, const char* message, bool monospaced_font)
void show_error(wxWindow* parent, const char* message, bool has_code_excerpts)
{
assert(message);
show_error(parent, wxString::FromUTF8(message), monospaced_font);
show_error(parent, wxString::FromUTF8(message), has_code_excerpts);
}
void show_error_id(int id, const std::string& message)
+5 -5
View File
@@ -40,11 +40,11 @@ extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_
// Change option value in config
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0);
// If monospaced_font is true, the error message is displayed using html <code><pre></pre></code> tags,
// so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
void show_error(wxWindow* parent, const wxString& message, bool monospaced_font = false);
void show_error(wxWindow* parent, const char* message, bool monospaced_font = false);
inline void show_error(wxWindow* parent, const std::string& message, bool monospaced_font = false) { show_error(parent, message.c_str(), monospaced_font); }
// If has_code_excerpts is true, code excerpts (a source line and the caret line below it) render
// monospaced so the caret aligns. Used for placeholder-parser errors.
void show_error(wxWindow* parent, const wxString& message, bool has_code_excerpts = false);
void show_error(wxWindow* parent, const char* message, bool has_code_excerpts = false);
inline void show_error(wxWindow* parent, const std::string& message, bool has_code_excerpts = false) { show_error(parent, message.c_str(), has_code_excerpts); }
void show_error_id(int id, const std::string& message); // For Perl
void show_info(wxWindow* parent, const wxString& message, const wxString& title = wxString());
void show_info(wxWindow* parent, const char* message, const char* title = nullptr);
+14
View File
@@ -307,6 +307,20 @@ public:
#endif // !__APPLE__
)
{
// Some desktop environments ignore splash screen typed window properties
// when running the app through Wayland,resulting in the titlebar being shown
// on the splash screen. The code below creates a client-side window decoration
// when running on Wayland and then removes that decoration. This ensures every
// environment correctly targets and removes the titlebar for this screen.
#if defined(__WXGTK__)
if (Slic3r::GUI::is_running_on_wayland()) {
GtkWidget *empty = gtk_fixed_new();
gtk_widget_set_size_request(empty, 0, 0);
gtk_window_set_titlebar(GTK_WINDOW(GetHandle()), empty);
gtk_window_set_decorated(GTK_WINDOW(GetHandle()), false);
}
#endif
this->SetPosition(pos);
this->CenterOnScreen();
+1
View File
@@ -123,6 +123,7 @@ std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::PART_CATE
{"sparse_infill_density", "", 1},
{"fill_multiline", "", 1},
{"sparse_infill_pattern", "", 1},
{"sparse_infill_smooth_factor", "", 1},
{"lateral_lattice_angle_1", "", 1},
{"lateral_lattice_angle_2", "", 1},
{"infill_overhang_angle", "", 1},
+53 -1
View File
@@ -548,7 +548,7 @@ void RemoveButtonBorder(wxWindow* win)
GtkCssProvider* provider = gtk_css_provider_new();
const char* css =
"button {"
"button, button:hover, button:active, button:focus {"
" border: none;"
" outline: none;"
" box-shadow: none;"
@@ -589,6 +589,58 @@ void RemoveButtonBorder(wxWindow* win)
);
#endif
}
void RemoveInputBorder(wxWindow* win)
{
GtkWidget* widget = win->GetHandle();
if (!widget) return;
#if GTK_CHECK_VERSION(3, 0, 0)
// GTK3+: use CSS provider
GtkCssProvider* provider = gtk_css_provider_new();
// Target 'entry' and its inner subnodes (like text selection areas)
const char* css =
"entry, entry text, entry undershoot {"
" border: none;"
" outline: none;"
" box-shadow: none;"
" padding: 0px;"
" margin: 0px;"
" min-height: 0px;"
" min-width: 0px;"
" background: none;"
"}";
#if GTK_CHECK_VERSION(4, 0, 0)
// GTK4
gtk_css_provider_load_from_data(provider, css, -1);
#else
// GTK3
gtk_css_provider_load_from_data(provider, css, -1, nullptr);
#endif
GtkStyleContext* ctx = gtk_widget_get_style_context(widget);
gtk_style_context_add_provider(
ctx,
GTK_STYLE_PROVIDER(provider),
GTK_STYLE_PROVIDER_PRIORITY_USER
);
g_object_unref(provider);
#else
// GTK2: Target the x/y thickness of the entry widget
gtk_rc_parse_string(
"style \"no-padding-entry\" {"
" xthickness = 0"
" ythickness = 0"
" GtkEntry::inner-border = { 0, 0, 0, 0 }"
" GtkEntry::focus-line-width = 0"
"}"
"class \"GtkEntry\" style \"no-padding-entry\""
);
#endif
}
#endif // __WXGTK__
#ifdef __linux__
+18 -12
View File
@@ -113,14 +113,7 @@ public:
update_dark_ui(this);
#endif
// Linux specific issue : get_dpi_for_window(this) still doesn't responce to the Display's scale in new wxWidgets(3.1.3).
// So, calculate the m_em_unit value from the font size, as before
#if !defined(__WXGTK__)
m_em_unit = std::max<size_t>(10, 10.0f * m_scale_factor);
#else
// initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
m_em_unit = std::max<size_t>(10, this->GetTextExtent("m").x - 1);
#endif // __WXGTK__
update_em_unit();
// recalc_font();
@@ -235,6 +228,19 @@ private:
// m_em_unit = metrics.averageWidth;
// }
// update em_unit value for new window font
void update_em_unit()
{
// Linux specific issue : get_dpi_for_window(this) still doesn't responce to the Display's scale in new wxWidgets(3.1.3).
// So, calculate the m_em_unit value from the font size, as before
#if !defined(__WXGTK__)
m_em_unit = std::max<size_t>(10, 10.0f * m_scale_factor);
#else
// initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
m_em_unit = std::max<size_t>(10, this->GetTextExtent("m").x - 1);
#endif // __WXGTK__
}
// check if new scale is differ from previous
bool is_new_scale_factor() const { return fabs(m_scale_factor - m_prev_scale_factor) > 0.001; }
@@ -247,8 +253,7 @@ private:
// set normal application font as a current window font
m_normal_font = this->GetFont();
// update em_unit value for new window font
m_em_unit = std::max<int>(10, 10.0f * m_scale_factor);
update_em_unit();
// rescale missed controls sizes and images
on_dpi_changed(suggested_rect);
@@ -472,8 +477,9 @@ void dataview_remove_insets(wxDataViewCtrl* dv);
void staticbox_remove_margin(wxStaticBox* sb);
#endif
#ifdef __WXGTK3__
void RemoveButtonBorder(wxWindow* win);
#ifdef __WXGTK__
void RemoveButtonBorder(wxWindow* win); // for wxButton/wxBitmapToggleButton based controls (SwitchButton, CheckBox)
void RemoveInputBorder(wxWindow* win); // for TextCtrl based controls (TextInput, ComboBox, SpinInput..)
#endif
#if defined(__WXOSX__) || defined(__linux__)
+1 -1
View File
@@ -442,7 +442,7 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) {
}
} else if (m_dragging) {
// when mouse cursor leave window than finish actual dragging operation
bool is_leaving = mouse_event.Leaving();
bool is_leaving = mouse_event.Leaving() && !m_parent.has_mouse_capture(); // ORCA keep tracking mouse position while drag active and cursor not in window bounds
if (mouse_event.Dragging()) {
Point mouse_coord(mouse_event.GetX(), mouse_event.GetY());
auto ray = m_parent.mouse_ray(mouse_coord);
+37 -33
View File
@@ -15,6 +15,8 @@ static const ColorRGBA DEF_COLOR = {0.7f, 0.7f, 0.7f, 1.f};
static const ColorRGBA SELECTED_COLOR = {0.0f, 0.5f, 0.5f, 1.0f};
static const ColorRGBA ERR_COLOR = {1.0f, 0.3f, 0.3f, 0.5f};
static const ColorRGBA HOVER_COLOR = {0.7f, 0.7f, 0.7f, 0.5f};
static constexpr float BRIM_EAR_RADIUS_MIN = 0.1f;
static constexpr float BRIM_EAR_RADIUS_MAX = 100.f;
static ModelVolume *get_model_volume(const Selection &selection, Model &model)
{
@@ -41,14 +43,14 @@ GLGizmoBrimEars::GLGizmoBrimEars(GLCanvas3D &parent, const std::string &icon_fil
bool GLGizmoBrimEars::on_init()
{
m_new_point_head_diameter = get_brim_default_radius();
m_new_point_head_radius = get_brim_default_radius();
m_shortcut_key = WXK_CONTROL_E;
const wxString ctrl = GUI::shortkey_ctrl_prefix();
const wxString alt = GUI::shortkey_alt_prefix();
m_desc["head_diameter"] = _L("Head diameter");
m_desc["brim_ear_radius"] = _L("Brim ear radius");
m_desc["max_angle"] = _L("Max angle");
m_desc["detection_radius"] = _L("Detection radius");
m_desc["remove"] = _L("Remove");
@@ -62,7 +64,7 @@ bool GLGizmoBrimEars::on_init()
m_shortcuts = {
{_L("Left mouse button"), _L("Add or Select")},
{_L("Right mouse button"), _L("Remove")},
{ctrl + _L("Mouse wheel"), m_desc["head_diameter"]},
{ctrl + _L("Mouse wheel"), m_desc["brim_ear_radius"]},
{alt + _L("Mouse wheel"), m_desc["section_view"]},
};
@@ -358,7 +360,7 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
Transform3d inverse_trsf = volume->get_instance_transformation().get_matrix_no_offset().inverse();
std::pair<Vec3f, Vec3f> pos_and_normal;
if (unproject_on_mesh2(mouse_position, pos_and_normal)) {
render_hover_point = CacheEntry(BrimPoint(pos_and_normal.first, m_new_point_head_diameter / 2.f), false, (inverse_trsf * m_world_normal).cast<float>(), true);
render_hover_point = CacheEntry(BrimPoint(pos_and_normal.first, m_new_point_head_radius), false, (inverse_trsf * m_world_normal).cast<float>(), true);
} else {
render_hover_point.reset();
}
@@ -397,7 +399,7 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
Vec3d object_pos = trsf.inverse() * world_pos;
// brim ear always face up
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Add brim ear");
add_point_to_cache(object_pos.cast<float>(), m_new_point_head_diameter / 2.f, false, (inverse_trsf * m_world_normal).cast<float>());
add_point_to_cache(object_pos.cast<float>(), m_new_point_head_radius, false, (inverse_trsf * m_world_normal).cast<float>());
m_parent.set_as_dirty();
m_wait_for_up_event = true;
find_single();
@@ -490,9 +492,9 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
// mouse wheel up
if (action == SLAGizmoEventType::MouseWheelUp) {
if (control_down) {
float initial_value = m_new_point_head_diameter;
float initial_value = m_new_point_head_radius;
begin_radius_change(initial_value);
m_new_point_head_diameter = std::min(20., initial_value + 0.1);
m_new_point_head_radius = std::min(BRIM_EAR_RADIUS_MAX, initial_value + 0.1f);
update_cache_radius();
return true;
}
@@ -502,9 +504,9 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
if (action == SLAGizmoEventType::MouseWheelDown) {
if (control_down) {
float initial_value = m_new_point_head_diameter;
float initial_value = m_new_point_head_radius;
begin_radius_change(initial_value);
m_new_point_head_diameter = std::max(5., initial_value - 0.1);
m_new_point_head_radius = std::max(BRIM_EAR_RADIUS_MIN, initial_value - 0.1f);
update_cache_radius();
return true;
}
@@ -597,18 +599,18 @@ std::vector<const ConfigOption *> GLGizmoBrimEars::get_config_options(const std:
void GLGizmoBrimEars::begin_radius_change(float initial_value)
{
if (m_old_point_head_diameter == 0.f)
m_old_point_head_diameter = initial_value;
if (m_old_point_head_radius == 0.f)
m_old_point_head_radius = initial_value;
}
void GLGizmoBrimEars::update_cache_radius()
{
if (render_hover_point)
render_hover_point->brim_point.head_front_radius = m_new_point_head_diameter / 2.f;
render_hover_point->brim_point.head_front_radius = m_new_point_head_radius;
for (auto &cache_entry : m_editing_cache)
if (cache_entry.selected) {
cache_entry.brim_point.head_front_radius = m_new_point_head_diameter / 2.f;
cache_entry.brim_point.head_front_radius = m_new_point_head_radius;
find_single();
update_model_object();
}
@@ -617,18 +619,18 @@ void GLGizmoBrimEars::update_cache_radius()
void GLGizmoBrimEars::apply_radius_change()
{
if (m_old_point_head_diameter == 0.f) return;
if (m_old_point_head_radius == 0.f) return;
// momentarily restore the old value to take snapshot
for (auto& cache_entry : m_editing_cache)
if (cache_entry.selected)
cache_entry.brim_point.head_front_radius = m_old_point_head_diameter / 2.f;
float backup = m_new_point_head_diameter;
m_new_point_head_diameter = m_old_point_head_diameter;
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Change point head diameter");
m_new_point_head_diameter = backup;
cache_entry.brim_point.head_front_radius = m_old_point_head_radius;
float backup = m_new_point_head_radius;
m_new_point_head_radius = m_old_point_head_radius;
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Change brim ear radius");
m_new_point_head_radius = backup;
update_cache_radius();
m_old_point_head_diameter = 0.f;
m_old_point_head_radius = 0.f;
}
void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limit)
@@ -653,7 +655,7 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
float space_size = m_imgui->get_style_scaling() * 8;
std::vector<wxString> text_list = {m_desc["head_diameter"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"],
std::vector<wxString> text_list = {m_desc["brim_ear_radius"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"],
m_desc["create"], m_desc["remove"]};
float widest_text = m_imgui->find_widest_text(text_list);
float caption_size = widest_text + space_size + ImGui::GetStyle().WindowPadding.x;
@@ -680,11 +682,11 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
// - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene
// - take correct undo/redo snapshot after the user is done with moving the slider
ImGui::AlignTextToFramePadding();
float initial_value = m_new_point_head_diameter;
m_imgui->text(m_desc["head_diameter"]);
float initial_value = m_new_point_head_radius;
m_imgui->text(m_desc["brim_ear_radius"]);
ImGui::SameLine(caption_size);
ImGui::PushItemWidth(slider_width);
m_imgui->bbl_slider_float_style("##head_diameter", &m_new_point_head_diameter, 5, 20, "%.1f", 1.0f, true);
m_imgui->bbl_slider_float_style("##brim_ear_radius", &m_new_point_head_radius, BRIM_EAR_RADIUS_MIN, BRIM_EAR_RADIUS_MAX, "%.1f", 1.0f, true);
if (m_imgui->get_last_slider_status().clicked) {
begin_radius_change(initial_value);
}
@@ -695,7 +697,7 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
}
ImGui::SameLine(drag_left_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##head_diameter_input", &m_new_point_head_diameter, 0.05f, 0.0f, 0.0f, "%.1f");
ImGui::BBLDragFloat("##brim_ear_radius_input", &m_new_point_head_radius, 0.05f, BRIM_EAR_RADIUS_MIN, BRIM_EAR_RADIUS_MAX, "%.1f");
ImGui::Separator();
@@ -910,9 +912,9 @@ void GLGizmoBrimEars::on_stop_dragging()
m_point_before_drag = CacheEntry();
}
void GLGizmoBrimEars::on_load(cereal::BinaryInputArchive &ar) { ar(m_new_point_head_diameter, m_editing_cache, m_selection_empty); }
void GLGizmoBrimEars::on_load(cereal::BinaryInputArchive &ar) { ar(m_new_point_head_radius, m_editing_cache, m_selection_empty); }
void GLGizmoBrimEars::on_save(cereal::BinaryOutputArchive &ar) const { ar(m_new_point_head_diameter, m_editing_cache, m_selection_empty); }
void GLGizmoBrimEars::on_save(cereal::BinaryOutputArchive &ar) const { ar(m_new_point_head_radius, m_editing_cache, m_selection_empty); }
void GLGizmoBrimEars::select_point(int i)
{
@@ -920,11 +922,11 @@ void GLGizmoBrimEars::select_point(int i)
for (auto &point_and_selection : m_editing_cache) point_and_selection.selected = (i == AllPoints);
m_selection_empty = (i == NoPoints);
if (i == AllPoints) m_new_point_head_diameter = m_editing_cache[0].brim_point.head_front_radius * 2.f;
if (i == AllPoints) m_new_point_head_radius = m_editing_cache[0].brim_point.head_front_radius;
} else {
m_editing_cache[i].selected = true;
m_selection_empty = false;
m_new_point_head_diameter = m_editing_cache[i].brim_point.head_front_radius * 2.f;
m_new_point_head_radius = m_editing_cache[i].brim_point.head_front_radius;
}
}
@@ -1011,8 +1013,7 @@ void GLGizmoBrimEars::auto_generate()
auto add_point = [this, &trsf, &normal](const Point &p) {
Vec3d world_pos = {float(p.x() * SCALING_FACTOR), float(p.y() * SCALING_FACTOR), -0.0001};
Vec3d object_pos = trsf.inverse() * world_pos;
// m_editing_cache.emplace_back(BrimPoint(object_pos.cast<float>(), m_new_point_head_diameter / 2), false, normal);
add_point_to_cache(object_pos.cast<float>(), m_new_point_head_diameter / 2, false, normal);
add_point_to_cache(object_pos.cast<float>(), m_new_point_head_radius, false, normal);
};
for (const ExPolygon &ex_poly : m_first_layer) {
Polygon out_poly = ex_poly.contour;
@@ -1158,8 +1159,11 @@ void GLGizmoBrimEars::reset_all_pick() { std::map<GLVolume *, std::shared_ptr<Pi
float GLGizmoBrimEars::get_brim_default_radius() const
{
const double nozzle_diameter = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->get_at(0);
const DynamicPrintConfig &pring_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
return pring_cfg.get_abs_value("initial_layer_line_width", nozzle_diameter) * 16.0f;
const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
return std::clamp(
float(print_cfg.get_abs_value("initial_layer_line_width", nozzle_diameter) * 8.0),
BRIM_EAR_RADIUS_MIN,
BRIM_EAR_RADIUS_MAX);
}
ExPolygon GLGizmoBrimEars::make_polygon(BrimPoint point, const Geometry::Transformation &trsf)
+2 -2
View File
@@ -98,12 +98,12 @@ private:
void render_points(const Selection& selection);
float m_new_point_head_diameter; // Size of a new point.
float m_new_point_head_radius; // Radius of a new point.
float m_max_angle = 125.f;
float m_detection_radius = 1.f;
double m_detection_radius_max = .0f;
CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited
float m_old_point_head_diameter = 0.; // the same
float m_old_point_head_radius = 0.; // the same
mutable std::vector<CacheEntry> m_editing_cache; // a support point and whether it is currently selectedchanges or undo/redo
std::map<int, CacheEntry> m_single_brim;
ObjectID m_old_mo_id;
+5 -2
View File
@@ -566,8 +566,11 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event)
void GLGizmoEmboss::on_mouse_change_selection(const wxMouseEvent &mouse_event)
{
static bool was_dragging = true;
if ((mouse_event.LeftUp() || mouse_event.RightUp()) && !was_dragging) {
static bool was_dragging = true;
// The left up may be the end of a drag that started on the gizmo floating window (e.g. selecting
// text in the input field). Such a release is not a click on the scene and must not close the gizmo.
// (The flag is only set for left up events, so right up behavior is unchanged.)
if ((mouse_event.LeftUp() || mouse_event.RightUp()) && !was_dragging && !m_parent.is_mouse_left_up_ignored()) {
// is hovered volume closest hovered?
int hovered_idx = m_parent.get_first_hover_volume_idx();
if (hovered_idx < 0)
+106 -18
View File
@@ -9,8 +9,13 @@
#include <wx/clipbrd.h>
#include <wx/checkbox.h>
#include <wx/html/htmlwin.h>
#include <wx/html/winpars.h>
#include <algorithm>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
@@ -229,12 +234,82 @@ void MsgDialog::finalize()
}
// A placeholder-parser caret line, pointing at the column where parsing failed.
static bool is_caret_line(const std::string &line)
{
return std::count(line.begin(), line.end(), '^') == 1 &&
std::all_of(line.begin(), line.end(), [](char c) { return c == ' ' || c == '^'; });
}
// Tag each line as a code excerpt (a caret line or the source line above one) that must stay
// monospaced for the '^' to align.
static std::vector<std::pair<std::string, bool>> classify_code_lines(const std::string &msg)
{
std::vector<std::string> lines;
boost::split(lines, msg, boost::is_any_of("\n"));
for (std::string &line : lines)
if (!line.empty() && line.back() == '\r')
line.pop_back();
std::vector<std::pair<std::string, bool>> tagged;
tagged.reserve(lines.size());
for (size_t i = 0; i < lines.size(); ++i) {
bool is_code = is_caret_line(lines[i]) || (i + 1 < lines.size() && is_caret_line(lines[i + 1]));
tagged.emplace_back(std::move(lines[i]), is_code);
}
return tagged;
}
// Keeps whitespace literal so the caret's leading spaces survive.
// Used inside <code>, which supplies the fixed face. <pre> does both but adds a blank line above it.
class CodeExcerptTagHandler : public wxHtmlWinTagHandler
{
public:
wxString GetSupportedTags() override { return wxT("EXCERPT"); }
bool HandleTag(const wxHtmlTag &tag) override
{
const wxHtmlWinParser::WhitespaceMode ws = m_WParser->GetWhitespaceMode();
m_WParser->SetWhitespaceMode(wxHtmlWinParser::Whitespace_Pre);
ParseInner(tag);
m_WParser->SetWhitespaceMode(ws);
return true;
}
};
// Render the message as HTML, monospacing only the code excerpts.
static std::string format_parser_error_html(const std::string &msg)
{
std::string out;
for (const auto &[text, is_code] : classify_code_lines(msg)) {
if (!out.empty()) out += "<br>"; // join, not trail; a trailing <br> forces a scrollbar
std::string escaped = xml_escape(text);
if (is_code)
out += "<code><excerpt>" + escaped + "</excerpt></code>";
else
out += escaped;
}
return out;
}
// Measure each line in the font it will render in, so the dialog fits the longest line without slack.
static wxSize measure_mixed_text(wxWindow *parent, const std::string &msg, const wxFont &prose_font, const wxFont &code_font)
{
wxClientDC dc(parent);
int width = 0, height = 0;
for (const auto &[text, is_code] : classify_code_lines(msg)) {
dc.SetFont(is_code ? code_font : prose_font);
width = std::max(width, dc.GetTextExtent(wxString::FromUTF8(text.c_str())).GetWidth());
height += dc.GetCharHeight();
}
return wxSize(width, height);
}
// Text shown as HTML, so that mouse selection and Ctrl-V to copy will work.
static void add_msg_content(wxWindow *parent,
wxBoxSizer *content_sizer,
wxString msg,
bool monospaced_font = false,
bool is_marked_msg = false,
bool has_code_excerpts = false,
bool is_marked_msg = false,
const wxString &link_text = "",
std::function<void(const wxString &)> link_callback = nullptr)
{
@@ -243,7 +318,7 @@ static void add_msg_content(wxWindow *parent,
// count lines in the message
int msg_lines = 0;
if (!monospaced_font) {
if (!has_code_excerpts) {
int line_len = 55;// count of symbols in one line
int start_line = 0;
for (auto i = msg.begin(); i != msg.end(); ++i) {
@@ -300,13 +375,23 @@ static void add_msg_content(wxWindow *parent,
page_size = wxSize(info_width, page_height);
}
else {
wxClientDC dc(parent);
dc.SetFont(font); // ORCA without this it calculates bigger size
wxSize msg_sz = dc.GetMultiLineTextExtent(msg) + parent->FromDIP(wxSize(10,5)); // added extra spacing to prevent wrapping
wxSize msg_sz;
if (has_code_excerpts) {
msg_sz = measure_mixed_text(parent, msg.ToUTF8().data(), font, monospace);
} else {
wxClientDC dc(parent);
dc.SetFont(font); // ORCA without this it calculates bigger size
msg_sz = dc.GetMultiLineTextExtent(msg);
}
msg_sz += parent->FromDIP(wxSize(10,5)); // added extra spacing to prevent wrapping
page_size = wxSize(std::min(msg_sz.GetX(), info_width), std::min(msg_sz.GetY(), info_width));
int page_height = msg_sz.GetY();
// Reserve the horizontal scrollbar's height, or it clips the last line.
if (msg_sz.GetX() > info_width)
page_height += wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y, parent);
page_size = wxSize(std::min(msg_sz.GetX(), info_width), std::min(page_height, info_width));
// Extra line breaks in message dialog
if (link_text.IsEmpty() && !link_callback && is_marked_msg == false) {//for common text
if (link_text.IsEmpty() && !link_callback && is_marked_msg == false && !has_code_excerpts) {//for common text
html->Destroy();
if (msg_sz.GetX() < info_width) {//No need for line breaks
info_width = msg_sz.GetX();
@@ -337,12 +422,15 @@ static void add_msg_content(wxWindow *parent,
}
html->SetMinSize(page_size);
std::string msg_escaped = xml_escape(msg.ToUTF8().data(), is_marked_msg);
boost::replace_all(msg_escaped, "\r\n", "<br>");
boost::replace_all(msg_escaped, "\n", "<br>");
if (monospaced_font)
// Code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
msg_escaped = std::string("<pre><code>") + msg_escaped + "</code></pre>";
std::string msg_escaped;
if (has_code_excerpts) {
html->GetParser()->AddTagHandler(new CodeExcerptTagHandler());
msg_escaped = format_parser_error_html(msg.ToUTF8().data());
} else {
msg_escaped = xml_escape(msg.ToUTF8().data(), is_marked_msg);
boost::replace_all(msg_escaped, "\r\n", "<br>");
boost::replace_all(msg_escaped, "\n", "<br>");
}
if (!link_text.IsEmpty() && link_callback) {
msg_escaped += "<span><a href=\"#\" style=\"color:rgb(0, 150, 136); text-decoration:underline;\">" + std::string(link_text.ToUTF8().data()) + "</a></span>";
@@ -360,15 +448,15 @@ static void add_msg_content(wxWindow *parent,
// ErrorDialog
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool monospaced_font)
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool has_code_excerpts)
: MsgDialog(parent, wxString::Format(_(L("%s error")), SLIC3R_APP_FULL_NAME),
wxString::Format(_(L("%s has encountered an error")), SLIC3R_APP_FULL_NAME), wxOK)
, msg(temp_msg)
{
add_msg_content(this, content_sizer, msg, monospaced_font);
add_msg_content(this, content_sizer, msg, has_code_excerpts);
// Use a small bitmap with monospaced font, as the error text will not be wrapped.
logo->SetBitmap(create_scaled_bitmap("OrcaSlicer_192px_grayscale.png", this, monospaced_font ? 48 : /*1*/64));
// Use a small bitmap for code excerpts, which cannot wrap and so need the width.
logo->SetBitmap(create_scaled_bitmap("OrcaSlicer_192px_grayscale.png", this, has_code_excerpts ? 48 : /*1*/64));
SetMaxSize(MSG_DLG_MAX_SIZE);
+3 -3
View File
@@ -106,9 +106,9 @@ protected:
class ErrorDialog : public MsgDialog
{
public:
// If monospaced_font is true, the error message is displayed using html <code><pre></pre></code> tags,
// so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool courier_font);
// If has_code_excerpts is true, code excerpts (a source line and the caret line below it) render
// monospaced so the caret aligns. Used for placeholder-parser errors.
ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool has_code_excerpts);
ErrorDialog(ErrorDialog &&) = delete;
ErrorDialog(const ErrorDialog &) = delete;
ErrorDialog &operator=(ErrorDialog &&) = delete;
+2
View File
@@ -386,6 +386,7 @@ void OptionsGroup::activate_line(Line& line)
}
if (label != nullptr && line.label_tooltip != "")
label->SetToolTip(line.label_tooltip);
line.label_widget = label;
}
}
@@ -574,6 +575,7 @@ void OptionsGroup::clear(bool destroy_custom_ctrl)
for (Line& line : m_lines) {
if (line.near_label_widget_win)
line.near_label_widget_win = nullptr;
line.label_widget = nullptr;
if (line.widget_sizer) {
line.widget_sizer->Clear(true);
+9
View File
@@ -62,6 +62,7 @@ public:
widget_t widget {nullptr};
std::function<wxWindow*(wxWindow*)> near_label_widget{ nullptr };
wxWindow* near_label_widget_win {nullptr};
wxStaticText* label_widget {nullptr};
wxSizer* widget_sizer {nullptr};
wxSizer* extra_widget_sizer {nullptr};
//BBS: export the extra colume widget
@@ -81,6 +82,14 @@ public:
label(_(label)), label_tooltip(_(tooltip)) {}
Line() : m_is_separator(true) {}
void set_label(const wxString& new_label) {
label = new_label;
if (label_widget != nullptr) {
label_widget->SetLabel(label + (label.IsEmpty() ? "" : ": "));
label_widget->Refresh();
}
}
bool is_separator() const { return m_is_separator; }
bool has_only_option(const std::string& opt_key) const { return m_options.size() == 1 && m_options[0].opt_id == opt_key; }
+3
View File
@@ -865,6 +865,9 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset
clr_picker = new wxBitmapButton(parent, wxID_ANY, {}, wxDefaultPosition, wxSize(FromDIP(20), FromDIP(20)), wxBU_EXACTFIT | wxBU_AUTODRAW | wxBORDER_NONE);
clr_picker->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
clr_picker->SetToolTip(_L("Click to select filament color"));
#ifdef __WXGTK__
RemoveButtonBorder(clr_picker);
#endif
clr_picker->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
// Check if it's an official filament
auto fila_type = Preset::remove_suffix_modified(GetValue().ToUTF8().data());
+15 -1
View File
@@ -1738,6 +1738,13 @@ void Tab::toggle_line(const std::string &opt_key, bool toggle, int opt_index)
if (line) line->toggle_visible = toggle;
};
void Tab::set_option_label(const std::string &opt_key, const wxString &label, int opt_index)
{
if (!m_active_page) return;
Line *line = m_active_page->get_line(opt_key, opt_index);
if (line) line->set_label(label);
}
// To be called by custom widgets, load a value into a config,
// update the preset selection boxes (the dirty flags)
// If value is saved before calling this function, put saved_value = true,
@@ -2816,6 +2823,7 @@ void TabPrint::build()
optgroup->append_single_option_line("fill_multiline", "strength_settings_infill#fill-multiline");
optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern");
optgroup->append_single_option_line("gyroid_optimized", "strength_settings_patterns#gyroid-optimized");
optgroup->append_single_option_line("sparse_infill_smooth_factor", "strength_settings_patterns#sparse-infill-smooth-factor");
optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction");
optgroup->append_single_option_line("sparse_infill_rotate_template", "strength_settings_infill_rotation_template_metalanguage");
optgroup->append_single_option_line("skin_infill_density", "strength_settings_patterns#locked-zag");
@@ -3069,6 +3077,7 @@ void TabPrint::build()
optgroup->append_single_option_line("combine_brims", "others_settings_brim#combine-brims");
optgroup->append_single_option_line("brim_ears_max_angle", "others_settings_brim#ear-max-angle");
optgroup->append_single_option_line("brim_ears_detection_length", "others_settings_brim#ear-detection-radius");
optgroup->append_single_option_line("brim_ears_outer_only", "others_settings_brim#brim-ears-outer-only");
optgroup = page->new_optgroup(L("Special mode"), L"param_special");
optgroup->append_single_option_line("slicing_mode", "others_settings_special_mode#slicing-mode");
@@ -5016,6 +5025,7 @@ void TabPrinter::build_fff()
optgroup->append_single_option_line("printer_structure", "printer_basic_information_advanced#printer-structure");
optgroup->append_single_option_line("gcode_flavor", "printer_basic_information_advanced#g-code-flavor");
optgroup->append_single_option_line("gcode_skip_config_block", "printer_basic_information_advanced#skip-g-code-config-block");
optgroup->append_single_option_line("pellet_modded_printer", "printer_basic_information_advanced#pellet-modded-printer");
optgroup->append_single_option_line("bbl_use_printhost", "printer_basic_information_advanced#use-3rd-party-print-host");
optgroup->append_single_option_line("use_3mf");
@@ -8945,11 +8955,15 @@ ConfigManipulation Tab::get_config_manipulation()
return toggle_line(opt_key, toggle, opt_index >= 0 ? opt_index + 256 : opt_index);
};
auto cb_set_option_label = [this](const t_config_option_key &opt_key, const wxString &label, int opt_index) {
return set_option_label(opt_key, label, opt_index >= 0 ? opt_index + 256 : opt_index);
};
auto cb_value_change = [this](const std::string& opt_key, const boost::any& value) {
return on_value_change(opt_key, value);
};
return ConfigManipulation(load_config, cb_toggle_field, cb_toggle_line, cb_value_change, nullptr, this);
return ConfigManipulation(load_config, cb_toggle_field, cb_toggle_line, cb_value_change, nullptr, this, cb_set_option_label);
}
+1
View File
@@ -402,6 +402,7 @@ public:
Field* get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1);
void toggle_option(const std::string &opt_key, bool toggle, int opt_index = -1);
void toggle_line(const std::string &opt_key, bool toggle, int opt_index = -1); // BBS: hide some line
void set_option_label(const std::string &opt_key, const wxString &label, int opt_index = -1);
wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText, wxString text = wxEmptyString);
bool current_preset_is_dirty() const;
bool saved_preset_is_dirty() const;
+2 -2
View File
@@ -2,7 +2,7 @@
#include "../wxExtensions.hpp"
#ifdef __WXGTK3__
#ifdef __WXGTK__
#include "../GUI_Utils.hpp"
#endif
@@ -29,7 +29,7 @@ CheckBox::CheckBox(wxWindow *parent, int id)
Bind(wxEVT_LEAVE_WINDOW, &CheckBox::updateBitmap, this);
#endif
#ifdef __WXGTK3__
#ifdef __WXGTK__
Slic3r::GUI::RemoveButtonBorder(this);
#endif
+5
View File
@@ -2,6 +2,10 @@
#include "../wxExtensions.hpp"
#ifdef __WXGTK__
#include "../GUI_Utils.hpp"
#endif
namespace Slic3r {
namespace GUI {
RadioBox::RadioBox(wxWindow *parent)
@@ -15,6 +19,7 @@ RadioBox::RadioBox(wxWindow *parent)
// Bind(wxEVT_TOGGLEBUTTON, [this](auto& e) { update(); e.Skip(); });
update();
#ifdef __WXGTK__
Slic3r::GUI::RemoveButtonBorder(this);
wxSize bestSize = GetBestSize();
bestSize.IncTo(m_on.GetBmpSize());
SetSize(bestSize);
+9
View File
@@ -5,6 +5,10 @@
#include <wx/dcgraph.h>
#ifdef __WXGTK__
#include "../GUI_Utils.hpp"
#endif
BEGIN_EVENT_TABLE(SpinInput, StaticBox)
EVT_KEY_DOWN(SpinInput::keyPressed)
@@ -58,6 +62,11 @@ void SpinInput::Create(wxWindow *parent,
state_handler.attach({&label_color, &text_color});
state_handler.update_binds();
text_ctrl = new TextCtrl(this, wxID_ANY, text, {20, 4}, wxDefaultSize, style | wxBORDER_NONE | wxTE_PROCESS_ENTER, wxTextValidator(wxFILTER_DIGITS));
#ifdef __WXGTK__
Slic3r::GUI::RemoveInputBorder(text_ctrl);
#endif
text_ctrl->SetFont(Label::Body_14);
text_ctrl->SetBackgroundColour(background_color.colorForStates(state_handler.states()));
text_ctrl->SetForegroundColour(text_color.colorForStates(state_handler.states()));
+2 -2
View File
@@ -12,7 +12,7 @@
#include "libslic3r/MacUtils.hpp"
#endif
#ifdef __WXGTK3__
#ifdef __WXGTK__
#include "../GUI_Utils.hpp"
#endif
@@ -37,7 +37,7 @@ SwitchButton::SwitchButton(wxWindow* parent, wxWindowID id)
Bind(wxEVT_TOGGLEBUTTON, [this](auto& e) { update(); e.Skip(); });
SetFont(Label::Body_12);
#ifdef __WXGTK3__
#ifdef __WXGTK__
Slic3r::GUI::RemoveButtonBorder(this);
#endif
+9
View File
@@ -6,6 +6,10 @@
#include <wx/dcclient.h>
#include <wx/dcgraph.h>
#ifdef __WXGTK__
#include "../GUI_Utils.hpp"
#endif
BEGIN_EVENT_TABLE(TextInput, StaticBox)
EVT_PAINT(TextInput::paintEvent)
@@ -60,6 +64,11 @@ void TextInput::Create(wxWindow * parent,
state_handler.attach({&label_color, & text_color});
state_handler.update_binds();
text_ctrl = new TextCtrl(this, wxID_ANY, text, {4, 4}, wxDefaultSize, style | wxBORDER_NONE | wxTE_PROCESS_ENTER);
#ifdef __WXGTK__
Slic3r::GUI::RemoveInputBorder(text_ctrl);
#endif
text_ctrl->SetFont(Label::Body_14);
text_ctrl->SetInitialSize(text_ctrl->GetBestSize());
text_ctrl->SetBackgroundColour(background_color.colorForStates(state_handler.states()));
+4
View File
@@ -1022,6 +1022,10 @@ ScalableButton::ScalableButton( wxWindow * parent,
m_width = size.x * 10 / em;
m_height= size.y * 10 / em;
}
#ifdef __WXGTK__
Slic3r::GUI::RemoveButtonBorder(this);
#endif
}