Merge branch 'main' into enh-port-edit-gcode-dlg

This commit is contained in:
Ocraftyone 2024-01-14 03:54:27 -05:00 committed by GitHub
commit 0af1b9b958
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 463 additions and 51 deletions

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="39"
height="22"
viewBox="0 0 39 22"
fill="none"
version="1.1"
id="svg5"
sodipodi:docname="camera_switch.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs5">
<clipPath
id="clip0_7112_29362">
<rect
id="svg_1"
fill="#ffffff"
height="30"
width="30"
x="0"
y="0" />
</clipPath>
</defs>
<sodipodi:namedview
id="namedview5"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="16"
inkscape:cx="6.84375"
inkscape:cy="11.21875"
inkscape:window-width="2560"
inkscape:window-height="1369"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg5"
showguides="false" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 21.066182,15.123836 h 1.463398 1.463399 1.463398 1.463399 m 0,0 h 1.463398 c 1.167463,0 2.113821,-0.946358 2.113821,-2.113821 V 2.600884 c 0,-1.1674317 -0.946358,-2.11382107 -2.113821,-2.11382107 H 16.675987 c -1.167411,0 -2.1138,0.94637877 -2.1138,2.11381047 v 1.3011427 1.3011428 1.3011427 1.3011427 h 1.268271 V 6.5043016 5.2031589 3.9020161 2.6008734 c 0,-0.4669642 0.378586,-0.8455179 0.845529,-0.8455179 h 11.707187 c 0.467048,0 0.845528,0.3785537 0.845528,0.8455285 v 10.409131 c 0,0.467049 -0.37848,0.845529 -0.845528,0.845529 h -1.463398 m 0,0 H 25.456377 23.992979 22.52958 21.066182 m 0,0 v 1.268292"
fill="#b3b3b5"
id="path1"
style="fill:#262e30;fill-opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none"
sodipodi:nodetypes="ccccccssssssccccccccsssssscccccccc" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 31.074174,7.804414 4.093731,3.449544 V 4.3548482 Z M 30.065035,6.9961839 c -0.501081,0.4222886 -0.501081,1.1942138 0,1.6164496 l 4.633179,3.9041215 c 0.687309,0.579187 1.737984,0.09058 1.737984,-0.80822 V 3.9003132 c 0,-0.8987967 -1.050675,-1.3873959 -1.737984,-0.80823 z"
fill="#b3b3b5"
id="path4"
style="fill:#262e30;fill-opacity:1;stroke-width:1.05691" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M 19.153511,8.3696736 H 7.4463245 c -0.4669431,0 -0.8455285,0.3785536 -0.8455285,0.8455179 V 19.624333 c 0,0.467049 0.3785854,0.845528 0.8455285,0.845528 H 19.153511 c 0.467048,0 0.845528,-0.378479 0.845528,-0.845528 V 9.215202 c 0,-0.4669748 -0.37848,-0.8455284 -0.845528,-0.8455284 z M 7.4463245,7.1013809 c -1.1674106,0 -2.1138,0.9463789 -2.1138,2.1138106 V 19.624333 c 0,1.167463 0.9463894,2.113821 2.1138,2.113821 H 19.153511 c 1.167463,0 2.113821,-0.946358 2.113821,-2.113821 V 9.215202 c 0,-1.1674317 -0.946358,-2.1138211 -2.113821,-2.1138211 z"
fill="#b3b3b5"
id="path1-5"
style="stroke:none;stroke-width:0;stroke-dasharray:none;fill:#262e30;fill-opacity:1" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 21.844511,14.418731 4.093731,3.449545 v -6.89911 z m -1.009139,-0.80823 c -0.501081,0.422289 -0.501081,1.194214 0,1.61645 l 4.633179,3.904122 c 0.687309,0.579187 1.737984,0.09058 1.737984,-0.80822 v -7.808222 c 0,-0.8987971 -1.050675,-1.3873956 -1.737984,-0.8082304 z"
fill="#b3b3b5"
id="path4-1"
style="stroke-width:1.05691;fill:#262e30;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="39"
height="22"
viewBox="0 0 39 22"
fill="none"
version="1.1"
id="svg5"
sodipodi:docname="camera_switch_dark.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs5">
<clipPath
id="clip0_7112_29362">
<rect
id="svg_1"
fill="#ffffff"
height="30"
width="30"
x="0"
y="0" />
</clipPath>
</defs>
<sodipodi:namedview
id="namedview5"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="16"
inkscape:cx="6.84375"
inkscape:cy="16.21875"
inkscape:window-width="2560"
inkscape:window-height="1369"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg5"
showguides="false" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 21.066182,15.123836 h 1.463398 1.463399 1.463398 1.463399 m 0,0 h 1.463398 c 1.167463,0 2.113821,-0.946358 2.113821,-2.113821 V 2.600884 c 0,-1.1674317 -0.946358,-2.11382107 -2.113821,-2.11382107 H 16.675987 c -1.167411,0 -2.1138,0.94637877 -2.1138,2.11381047 v 1.3011427 1.3011428 1.3011427 1.3011427 h 1.268271 V 6.5043016 5.2031589 3.9020161 2.6008734 c 0,-0.4669642 0.378586,-0.8455179 0.845529,-0.8455179 h 11.707187 c 0.467048,0 0.845528,0.3785537 0.845528,0.8455285 v 10.409131 c 0,0.467049 -0.37848,0.845529 -0.845528,0.845529 h -1.463398 m 0,0 H 25.456377 23.992979 22.52958 21.066182 m 0,0 v 1.268292"
fill="#b3b3b5"
id="path1"
style="fill:#b3b3b5;fill-opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none"
sodipodi:nodetypes="ccccccssssssccccccccsssssscccccccc" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 31.074174,7.804414 4.093731,3.449544 V 4.3548482 Z M 30.065035,6.9961839 c -0.501081,0.4222886 -0.501081,1.1942138 0,1.6164496 l 4.633179,3.9041215 c 0.687309,0.579187 1.737984,0.09058 1.737984,-0.80822 V 3.9003132 c 0,-0.8987967 -1.050675,-1.3873959 -1.737984,-0.80823 z"
fill="#b3b3b5"
id="path4"
style="fill:#b3b3b5;fill-opacity:1;stroke-width:1.05691" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M 19.153511,8.3696736 H 7.4463245 c -0.4669431,0 -0.8455285,0.3785536 -0.8455285,0.8455179 V 19.624333 c 0,0.467049 0.3785854,0.845528 0.8455285,0.845528 H 19.153511 c 0.467048,0 0.845528,-0.378479 0.845528,-0.845528 V 9.215202 c 0,-0.4669748 -0.37848,-0.8455284 -0.845528,-0.8455284 z M 7.4463245,7.1013809 c -1.1674106,0 -2.1138,0.9463789 -2.1138,2.1138106 V 19.624333 c 0,1.167463 0.9463894,2.113821 2.1138,2.113821 H 19.153511 c 1.167463,0 2.113821,-0.946358 2.113821,-2.113821 V 9.215202 c 0,-1.1674317 -0.946358,-2.1138211 -2.113821,-2.1138211 z"
fill="#b3b3b5"
id="path1-5"
style="stroke:none;stroke-width:0;stroke-dasharray:none;fill:#b3b3b5;fill-opacity:1" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 21.844511,14.418731 4.093731,3.449545 v -6.89911 z m -1.009139,-0.80823 c -0.501081,0.422289 -0.501081,1.194214 0,1.61645 l 4.633179,3.904122 c 0.687309,0.579187 1.737984,0.09058 1.737984,-0.80822 v -7.808222 c 0,-0.8987971 -1.050675,-1.3873956 -1.737984,-0.8082304 z"
fill="#b3b3b5"
id="path4-1"
style="stroke-width:1.05691;fill:#b3b3b5;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -2478,9 +2478,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
CalibPressureAdvanceLine pa_test(this);
auto fast_speed = CalibPressureAdvance::find_optimal_PA_speed(print.full_print_config(), pa_test.line_width(), 0.2);
auto slow_speed = std::max(20.0, fast_speed / 10.0);
auto fast_speed = CalibPressureAdvance::find_optimal_PA_speed(print.full_print_config(), pa_test.line_width(), pa_test.height_layer());
auto slow_speed = std::max(10.0, fast_speed / 10.0);
if (fast_speed < slow_speed + 5)
fast_speed = slow_speed + 5;
pa_test.set_speed(fast_speed, slow_speed);
pa_test.draw_numbers() = print.calib_params().print_numbers;
gcode += pa_test.generate_test(params.start, params.step, std::llround(std::ceil((params.end - params.start) / params.step)) + 1);

View file

@ -786,7 +786,7 @@ static std::vector<std::string> s_Preset_print_options {
"independent_support_layer_height",
"support_angle", "support_interface_top_layers", "support_interface_bottom_layers",
"support_interface_pattern", "support_interface_spacing", "support_interface_loop_pattern",
"support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "bridge_no_support", "thick_bridges", "thick_internal_bridges", "max_bridge_length", "print_sequence", "support_remove_small_overhang",
"support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "bridge_no_support", "thick_bridges", "thick_internal_bridges","dont_filter_internal_bridges", "max_bridge_length", "print_sequence", "support_remove_small_overhang",
"filename_format", "wall_filament", "support_bottom_z_distance",
"sparse_infill_filament", "solid_infill_filament", "support_filament", "support_interface_filament","support_interface_not_for_body",
"ooze_prevention", "standby_temperature_delta", "interface_shells", "line_width", "initial_layer_line_width",

View file

@ -248,6 +248,13 @@ static t_config_enum_values s_keys_map_SeamPosition {
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SeamPosition)
static t_config_enum_values s_keys_map_InternalBridgeFilter {
{ "disabled", ibfDisabled },
{ "limited", ibfLimited },
{ "nofilter", ibfNofilter },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InternalBridgeFilter)
static const t_config_enum_values s_keys_map_SLADisplayOrientation = {
{ "landscape", sladoLandscape},
{ "portrait", sladoPortrait}
@ -1220,6 +1227,32 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true));
def = this->add("dont_filter_internal_bridges", coEnum);
def->label = L("Don't filter out small internal bridges (experimental)");
def->category = L("Quality");
def->tooltip = L("This option can help reducing pillowing on top surfaces in heavily slanted or curved models.\n\n"
"By default, small internal bridges are filtered out and the internal solid infill is printed directly"
" over the sparse infill. This works well in most cases, speeding up printing without too much compromise"
" on top surface quality. \n\nHowever, in heavily slanted or curved models especially where too low sparse"
" infill density is used, this may result in curling of the unsupported solid infill, causing pillowing.\n\n"
"Enabling this option will print internal bridge layer over slightly unsupported internal"
" solid infill. The options below control the amount of filtering, i.e. the amount of internal bridges "
"created.\n\n"
"Disabled - Disables this option. This is the default behaviour and works well in most cases.\n\n"
"Limited filtering - Creates internal bridges on heavily slanted surfaces, while avoiding creating "
"uncessesary interal bridges. This works well for most difficult models.\n\n"
"No filtering - Creates internal bridges on every potential internal overhang. This option is useful "
"for heavily slanted top surface models. However, in most cases it creates too many unecessary bridges.");
def->enum_keys_map = &ConfigOptionEnum<InternalBridgeFilter>::get_enum_values();
def->enum_values.push_back("disabled");
def->enum_values.push_back("limited");
def->enum_values.push_back("nofilter");
def->enum_labels.push_back(L("Disabled"));
def->enum_labels.push_back(L("Limited filtering"));
def->enum_labels.push_back(L("No filtering"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<InternalBridgeFilter>(ibfDisabled));
def = this->add("max_bridge_length", coFloat);
def->label = L("Max bridge length");

View file

@ -152,6 +152,10 @@ enum SeamPosition {
spNearest, spAligned, spRear, spRandom
};
enum InternalBridgeFilter {
ibfDisabled, ibfLimited, ibfNofilter
};
enum LiftType {
NormalLift,
SpiralLift,
@ -744,6 +748,7 @@ PRINT_CONFIG_CLASS_DEFINE(
// Orca internal thick bridge
((ConfigOptionBool, thick_bridges))
((ConfigOptionBool, thick_internal_bridges))
((ConfigOptionEnum<InternalBridgeFilter>, dont_filter_internal_bridges))
// Overhang angle threshold.
((ConfigOptionInt, support_threshold_angle))
((ConfigOptionFloat, support_object_xy_distance))

View file

@ -1969,7 +1969,6 @@ template<typename T> void debug_draw(std::string name, const T& a, const T& b, c
void PrintObject::bridge_over_infill()
{
BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Start" << log_memory_info();
struct CandidateSurface
{
CandidateSurface(const Surface *original_surface,
@ -1991,12 +1990,20 @@ void PrintObject::bridge_over_infill()
};
std::map<size_t, std::vector<CandidateSurface>> surfaces_by_layer;
// Orca:
// Detect use of lightning infill. Moved earlier in the function to pass to the gather and filter surfaces threads.
bool has_lightning_infill = false;
for (size_t i = 0; i < this->num_printing_regions(); i++) {
if (this->printing_region(i).config().sparse_infill_pattern == ipLightning) {
has_lightning_infill = true;
break;
}
}
// SECTION to gather and filter surfaces for expanding, and then cluster them by layer
{
tbb::concurrent_vector<CandidateSurface> candidate_surfaces;
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = static_cast<const PrintObject *>(this),
&candidate_surfaces](tbb::blocked_range<size_t> r) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = static_cast<const PrintObject *>(this), &candidate_surfaces, has_lightning_infill](tbb::blocked_range<size_t> r) {
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
for (size_t lidx = r.begin(); lidx < r.end(); lidx++) {
const Layer *layer = po->get_layer(lidx);
@ -2019,44 +2026,60 @@ void PrintObject::bridge_over_infill()
}
}
unsupported_area = closing(unsupported_area, float(SCALED_EPSILON));
// Orca:
// Lightning infill benefits from always having a bridge layer so don't filter out small unsupported areas. Also, don't filter small internal unsupported areas if the user has requested so.
double expansion_multiplier = 3;
if(has_lightning_infill || po->config().dont_filter_internal_bridges.value !=ibfDisabled){
expansion_multiplier = 1;
}
// By expanding the lower layer solids, we avoid making bridges from the tiny internal overhangs that are (very likely) supported by previous layer solids
// NOTE that we cannot filter out polygons worth bridging by their area, because sometimes there is a very small internal island that will grow into large hole
lower_layer_solids = shrink(lower_layer_solids, 1 * spacing); // first remove thin regions that will not support anything
lower_layer_solids = expand(lower_layer_solids, (1 + 3) * spacing); // then expand back (opening), and further for parts supported by internal solids
lower_layer_solids = expand(lower_layer_solids, (1 + expansion_multiplier) * spacing); // then expand back (opening), and further for parts supported by internal solids
// By shrinking the unsupported area, we avoid making bridges from narrow ensuring region along perimeters.
unsupported_area = shrink(unsupported_area, 3 * spacing);
unsupported_area = shrink(unsupported_area, expansion_multiplier * spacing);
unsupported_area = diff(unsupported_area, lower_layer_solids);
for (const LayerRegion *region : layer->regions()) {
SurfacesPtr region_internal_solids = region->fill_surfaces.filter_by_type(stInternalSolid);
for (const Surface *s : region_internal_solids) {
Polygons unsupported = intersection(to_polygons(s->expolygon), unsupported_area);
// The following flag marks those surfaces, which overlap with unuspported area, but at least part of them is supported.
// These regions can be filtered by area, because they for sure are touching solids on lower layers, and it does not make sense to bridge their tiny overhangs
bool partially_supported = area(unsupported) < area(to_polygons(s->expolygon)) - EPSILON;
if (!unsupported.empty() && (!partially_supported || area(unsupported) > 3 * 3 * spacing * spacing)) {
Polygons worth_bridging = intersection(to_polygons(s->expolygon), expand(unsupported, 4 * spacing));
// after we extracted the part worth briding, we go over the leftovers and merge the tiny ones back, to not brake the surface too much
for (const Polygon& p : diff(to_polygons(s->expolygon), expand(worth_bridging, spacing))) {
double area = p.area();
if (area < spacing * scale_(12.0) && area > spacing * spacing) {
worth_bridging.push_back(p);
// Orca: If the user has selected to always support internal overhanging regions, no matter how small
// skip the filtering
if (po->config().dont_filter_internal_bridges.value == ibfNofilter){
// expand the unsupported area by 4x spacing to trigger internal bridging
unsupported = expand(unsupported, 4 * spacing);
candidate_surfaces.push_back(CandidateSurface(s, lidx, unsupported, region, 0));
}else{
// The following flag marks those surfaces, which overlap with unuspported area, but at least part of them is supported.
// These regions can be filtered by area, because they for sure are touching solids on lower layers, and it does not make sense to bridge their tiny overhangs
bool partially_supported = area(unsupported) < area(to_polygons(s->expolygon)) - EPSILON;
if (!unsupported.empty() && (!partially_supported || area(unsupported) > 3 * 3 * spacing * spacing)) {
Polygons worth_bridging = intersection(to_polygons(s->expolygon), expand(unsupported, 4 * spacing));
// after we extracted the part worth briding, we go over the leftovers and merge the tiny ones back, to not brake the surface too much
for (const Polygon& p : diff(to_polygons(s->expolygon), expand(worth_bridging, spacing))) {
double area = p.area();
if (area < spacing * scale_(12.0) && area > spacing * spacing) {
worth_bridging.push_back(p);
}
}
worth_bridging = intersection(closing(worth_bridging, float(SCALED_EPSILON)), s->expolygon);
candidate_surfaces.push_back(CandidateSurface(s, lidx, worth_bridging, region, 0));
#ifdef DEBUG_BRIDGE_OVER_INFILL
debug_draw(std::to_string(lidx) + "_candidate_surface_" + std::to_string(area(s->expolygon)),
to_lines(region->layer()->lslices), to_lines(s->expolygon), to_lines(worth_bridging),
to_lines(unsupported_area));
#endif
#ifdef DEBUG_BRIDGE_OVER_INFILL
debug_draw(std::to_string(lidx) + "_candidate_processing_" + std::to_string(area(unsupported)),
to_lines(unsupported), to_lines(intersection(to_polygons(s->expolygon), expand(unsupported, 5 * spacing))),
to_lines(diff(to_polygons(s->expolygon), expand(worth_bridging, spacing))),
to_lines(unsupported_area));
#endif
}
worth_bridging = intersection(closing(worth_bridging, float(SCALED_EPSILON)), s->expolygon);
candidate_surfaces.push_back(CandidateSurface(s, lidx, worth_bridging, region, 0));
#ifdef DEBUG_BRIDGE_OVER_INFILL
debug_draw(std::to_string(lidx) + "_candidate_surface_" + std::to_string(area(s->expolygon)),
to_lines(region->layer()->lslices), to_lines(s->expolygon), to_lines(worth_bridging),
to_lines(unsupported_area));
#endif
#ifdef DEBUG_BRIDGE_OVER_INFILL
debug_draw(std::to_string(lidx) + "_candidate_processing_" + std::to_string(area(unsupported)),
to_lines(unsupported), to_lines(intersection(to_polygons(s->expolygon), expand(unsupported, 5 * spacing))),
to_lines(diff(to_polygons(s->expolygon), expand(worth_bridging, spacing))),
to_lines(unsupported_area));
#endif
}
}
}
@ -2071,13 +2094,6 @@ void PrintObject::bridge_over_infill()
// LIGHTNING INFILL SECTION - If lightning infill is used somewhere, we check the areas that are going to be bridges, and those that rely on the
// lightning infill under them get expanded. This somewhat helps to ensure that most of the extrusions are anchored to the lightning infill at the ends.
// It requires modifying this instance of print object in a specific way, so that we do not invalidate the pointers in our surfaces_by_layer structure.
bool has_lightning_infill = false;
for (size_t i = 0; i < this->num_printing_regions(); i++) {
if (this->printing_region(i).config().sparse_infill_pattern == ipLightning) {
has_lightning_infill = true;
break;
}
}
if (has_lightning_infill) {
// Prepare backup data for the Layer Region infills. Before modfiyng the layer region, we backup its fill surfaces by moving! them into this map.
// then a copy is created, modifiyed and passed to lightning infill generator. After generator is created, we restore the original state of the fills

View file

@ -424,6 +424,13 @@ std::string CalibPressureAdvance::draw_box(GCodeWriter &writer, double min_x, do
return gcode.str();
}
CalibPressureAdvanceLine::CalibPressureAdvanceLine(GCode* gcodegen)
: CalibPressureAdvance(gcodegen->config()), mp_gcodegen(gcodegen), m_nozzle_diameter(gcodegen->config().nozzle_diameter.get_at(0))
{
m_line_width = m_nozzle_diameter < 0.51 ? m_nozzle_diameter * 1.5 : m_nozzle_diameter * 1.05;
m_height_layer = gcodegen->config().initial_layer_print_height;
m_number_line_width = m_thin_line_width = m_nozzle_diameter;
};
std::string CalibPressureAdvanceLine::generate_test(double start_pa /*= 0*/, double step_pa /*= 0.002*/, int count /*= 10*/)
{
@ -496,14 +503,15 @@ std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double star
// gcode << move_to(Vec2d(start_x + m_length_short + m_length_long, y_pos + (num - 1) * m_space_y + 7), writer);
// gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long, y_pos + (num - 1) * m_space_y + 2), thin_e_per_mm * 7);
DrawBoxOptArgs default_box_opt_args(2, m_height_layer, 0.6, fast);
const auto box_start_x = start_x + m_length_short + m_length_long + m_length_short;
DrawBoxOptArgs default_box_opt_args(2, m_height_layer, m_line_width, fast);
default_box_opt_args.is_filled = true;
gcode << draw_box(writer, start_x + m_length_short + m_length_long + m_length_short, start_y - m_space_y, number_spacing() * 8,
(num + 1) * m_space_y, default_box_opt_args);
gcode << draw_box(writer, box_start_x, start_y - m_space_y,
number_spacing() * 8, (num + 1) * m_space_y, default_box_opt_args);
gcode << writer.travel_to_z(m_height_layer*2);
for (int i = 0; i < num; i += 2) {
gcode << draw_number(start_x + m_length_short + m_length_long + m_length_short + 3, y_pos + i * m_space_y + m_space_y / 2,
start_pa + i * step_pa, m_draw_digit_mode, m_number_line_width, number_e_per_mm, 3600, writer);
gcode << draw_number(box_start_x + 3 + m_line_width, y_pos + i * m_space_y + m_space_y / 2, start_pa + i * step_pa, m_draw_digit_mode,
m_number_line_width, number_e_per_mm, 3600, writer);
}
}
return gcode.str();

View file

@ -187,7 +187,7 @@ protected:
class CalibPressureAdvanceLine : public CalibPressureAdvance
{
public:
CalibPressureAdvanceLine(GCode *gcodegen) : CalibPressureAdvance(gcodegen->config()), mp_gcodegen(gcodegen),m_nozzle_diameter(gcodegen->config().nozzle_diameter.get_at(0)){};
CalibPressureAdvanceLine(GCode* gcodegen);
~CalibPressureAdvanceLine(){};
std::string generate_test(double start_pa = 0, double step_pa = 0.002, int count = 50);
@ -199,6 +199,7 @@ public:
}
const double &line_width() { return m_line_width; };
const double &height_layer() { return m_height_layer; };
bool is_delta() const;
bool &draw_numbers() { return m_draw_numbers; }
@ -212,10 +213,10 @@ private:
double m_nozzle_diameter;
double m_slow_speed, m_fast_speed;
const double m_height_layer{0.2};
const double m_line_width{0.6};
const double m_thin_line_width{0.44};
const double m_number_line_width{0.48};
double m_height_layer{0.2};
double m_line_width{0.6};
double m_thin_line_width{0.44};
double m_number_line_width{0.48};
const double m_space_y{3.5};
double m_length_short{20.0}, m_length_long{40.0};

View file

@ -23,6 +23,7 @@ wxEND_EVENT_TABLE()
wxDEFINE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
wxDEFINE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
wxDEFINE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent);
#define CAMERAPOPUP_CLICK_INTERVAL 20
@ -78,6 +79,34 @@ CameraPopup::CameraPopup(wxWindow *parent)
top_sizer->Add(0, 0, wxALL, 0);
}
// custom IP camera
m_custom_camera_input_confirm = new Button(m_panel, _L("Enable"));
m_custom_camera_input_confirm->SetBackgroundColor(wxColour(38, 166, 154));
m_custom_camera_input_confirm->SetBorderColor(wxColour(38, 166, 154));
m_custom_camera_input_confirm->SetTextColor(wxColour(0xFFFFFE));
m_custom_camera_input_confirm->SetFont(Label::Body_14);
m_custom_camera_input_confirm->SetMinSize(wxSize(FromDIP(90), FromDIP(30)));
m_custom_camera_input_confirm->SetPosition(wxDefaultPosition);
m_custom_camera_input_confirm->SetCornerRadius(FromDIP(12));
m_custom_camera_input = new TextInput(m_panel, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, wxDefaultSize);
m_custom_camera_input->GetTextCtrl()->SetHint(_L("Hostname or IP"));
m_custom_camera_input->GetTextCtrl()->SetFont(Label::Body_14);
m_custom_camera_hint = new wxStaticText(m_panel, wxID_ANY, _L("Custom camera source"));
m_custom_camera_hint->Wrap(-1);
m_custom_camera_hint->SetFont(Label::Head_14);
m_custom_camera_hint->SetForegroundColour(TEXT_COL);
m_custom_camera_input_confirm->Bind(wxEVT_BUTTON, &CameraPopup::on_camera_source_changed, this);
if (!wxGetApp().app_config->get("camera", "custom_source").empty()) {
m_custom_camera_input->GetTextCtrl()->SetValue(wxGetApp().app_config->get("camera", "custom_source"));
set_custom_cam_button_state(wxGetApp().app_config->get("camera", "enable_custom_source") == "true");
}
top_sizer->Add(m_custom_camera_hint, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5));
top_sizer->Add(0, 0, wxALL, 0);
top_sizer->Add(m_custom_camera_input, 2, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxEXPAND | wxALL, FromDIP(5));
top_sizer->Add(m_custom_camera_input_confirm, 1, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5));
main_sizer->Add(top_sizer, 0, wxALL, FromDIP(10));
auto url = wxString::Format(L"https://wiki.bambulab.com/%s/software/bambu-studio/virtual-camera", L"en");
@ -132,6 +161,37 @@ void CameraPopup::sdcard_absent_hint()
GetEventHandler()->ProcessEvent(evt);
}
void CameraPopup::on_camera_source_changed(wxCommandEvent &event)
{
if (m_obj && !m_custom_camera_input->GetTextCtrl()->IsEmpty()) {
handle_camera_source_change();
}
}
void CameraPopup::handle_camera_source_change()
{
m_custom_camera_enabled = !m_custom_camera_enabled;
set_custom_cam_button_state(m_custom_camera_enabled);
wxGetApp().app_config->set("camera", "custom_source", m_custom_camera_input->GetTextCtrl()->GetValue().ToStdString());
wxGetApp().app_config->set("camera", "enable_custom_source", m_custom_camera_enabled);
wxCommandEvent evt(EVT_CAM_SOURCE_CHANGE);
evt.SetEventObject(this);
GetEventHandler()->ProcessEvent(evt);
}
void CameraPopup::set_custom_cam_button_state(bool state)
{
m_custom_camera_enabled = state;
auto stateColour = state ? wxColour(170, 0, 0) : wxColour(38, 166, 154);
auto stateText = state ? "Disable" : "Enable";
m_custom_camera_input_confirm->SetBackgroundColor(stateColour);
m_custom_camera_input_confirm->SetBorderColor(stateColour);
m_custom_camera_input_confirm->SetLabel(_L(stateText));
}
void CameraPopup::on_switch_recording(wxCommandEvent& event)
{
if (!m_obj) return;

View file

@ -14,12 +14,14 @@
#include "Widgets/SwitchButton.hpp"
#include "Widgets/RadioBox.hpp"
#include "Widgets/PopupWindow.hpp"
#include "Widgets/TextInput.hpp"
namespace Slic3r {
namespace GUI {
wxDECLARE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
wxDECLARE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
wxDECLARE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent);
class CameraPopup : public PopupWindow
{
@ -50,6 +52,9 @@ protected:
void on_switch_recording(wxCommandEvent& event);
void on_set_resolution();
void sdcard_absent_hint();
void on_camera_source_changed(wxCommandEvent& event);
void handle_camera_source_change();
void set_custom_cam_button_state(bool state);
wxWindow * create_item_radiobox(wxString title, wxWindow *parent, wxString tooltip, int padding_left);
void select_curr_radiobox(int btn_idx);
@ -66,6 +71,10 @@ private:
SwitchButton* m_switch_recording;
wxStaticText* m_text_vcamera;
SwitchButton* m_switch_vcamera;
wxStaticText* m_custom_camera_hint;
TextInput* m_custom_camera_input;
Button* m_custom_camera_input_confirm;
bool m_custom_camera_enabled{ false };
wxStaticText* m_text_resolution;
wxWindow* m_resolution_options[RESOLUTION_OPTIONS_NUM];
wxScrolledWindow *m_panel;

View file

@ -5,6 +5,7 @@
#include "Widgets/Button.hpp"
#include "Widgets/StepCtrl.hpp"
#include "Widgets/SideTools.hpp"
#include "Widgets/WebView.hpp"
#include "BitmapCache.hpp"
#include "GUI_App.hpp"
@ -889,6 +890,11 @@ StatusBasePanel::StatusBasePanel(wxWindow *parent, wxWindowID id, const wxPoint
StatusBasePanel::~StatusBasePanel()
{
delete m_media_play_ctrl;
if (m_custom_camera_view) {
delete m_custom_camera_view;
m_custom_camera_view = nullptr;
}
}
void StatusBasePanel::init_bitmaps()
@ -922,6 +928,7 @@ void StatusBasePanel::init_bitmaps()
m_bitmap_timelapse_off = ScalableBitmap(this, wxGetApp().dark_mode() ? "monitor_timelapse_off_dark" : "monitor_timelapse_off", 20);
m_bitmap_vcamera_on = ScalableBitmap(this, wxGetApp().dark_mode() ? "monitor_vcamera_on_dark" : "monitor_vcamera_on", 20);
m_bitmap_vcamera_off = ScalableBitmap(this, wxGetApp().dark_mode() ? "monitor_vcamera_off_dark" : "monitor_vcamera_off", 20);
m_bitmap_switch_camera = ScalableBitmap(this, wxGetApp().dark_mode() ? "camera_switch_dark" : "camera_switch", 20);
}
@ -989,12 +996,27 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
m_setting_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24)));
m_setting_button->SetBackgroundColour(STATUS_TITLE_BG);
m_camera_switch_button = new wxStaticBitmap(m_panel_monitoring_title, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(FromDIP(38), FromDIP(24)), 0);
m_camera_switch_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24)));
m_camera_switch_button->SetBackgroundColour(STATUS_TITLE_BG);
m_camera_switch_button->SetBitmap(m_bitmap_switch_camera.bmp());
m_camera_switch_button->Bind(wxEVT_LEFT_DOWN, &StatusBasePanel::on_camera_switch_toggled, this);
m_camera_switch_button->Bind(wxEVT_RIGHT_DOWN, [this](auto& e) {
const std::string js_request_pip = R"(
document.querySelector('video').requestPictureInPicture();
)";
m_custom_camera_view->RunScript(js_request_pip);
});
m_camera_switch_button->Hide();
m_bitmap_sdcard_img->SetToolTip(_L("SD Card"));
m_bitmap_timelapse_img->SetToolTip(_L("Timelapse"));
m_bitmap_recording_img->SetToolTip(_L("Video"));
m_bitmap_vcamera_img->SetToolTip(_L("Go Live"));
m_setting_button->SetToolTip(_L("Camera Setting"));
m_camera_switch_button->SetToolTip(_L("Switch Camera View"));
bSizer_monitoring_title->Add(m_camera_switch_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
bSizer_monitoring_title->Add(m_bitmap_sdcard_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
bSizer_monitoring_title->Add(m_bitmap_timelapse_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
bSizer_monitoring_title->Add(m_bitmap_recording_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
@ -1014,17 +1036,44 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
m_media_ctrl = new wxMediaCtrl2(this);
m_media_ctrl->SetMinSize(wxSize(PAGE_MIN_WIDTH, FromDIP(288)));
m_custom_camera_view = WebView::CreateWebView(this, wxEmptyString);
m_custom_camera_view->EnableContextMenu(false);
Bind(wxEVT_WEBVIEW_NAVIGATING, &StatusBasePanel::on_webview_navigating, this, m_custom_camera_view->GetId());
m_media_play_ctrl = new MediaPlayCtrl(this, m_media_ctrl, wxDefaultPosition, wxSize(-1, FromDIP(40)));
m_custom_camera_view->Hide();
m_custom_camera_view->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, [this](wxWebViewEvent& evt) {
if (evt.GetString() == "leavepictureinpicture") {
// When leaving PiP, video gets paused in some cases and toggling play
// programmatically does not work.
m_custom_camera_view->Reload();
}
else if (evt.GetString() == "enterpictureinpicture") {
toggle_builtin_camera();
}
});
sizer->Add(m_media_ctrl, 1, wxEXPAND | wxALL, 0);
sizer->Add(m_custom_camera_view, 1, wxEXPAND | wxALL, 0);
sizer->Add(m_media_play_ctrl, 0, wxEXPAND | wxALL, 0);
// media_ctrl_panel->SetSizer(bSizer_monitoring);
// media_ctrl_panel->Layout();
//
// sizer->Add(media_ctrl_panel, 1, wxEXPAND | wxALL, 1);
if (wxGetApp().app_config->get("camera", "enable_custom_source") == "true") {
handle_camera_source_change();
}
return sizer;
}
void StatusBasePanel::on_webview_navigating(wxWebViewEvent& evt) {
wxGetApp().CallAfter([this] {
remove_controls();
});
}
wxBoxSizer *StatusBasePanel::create_machine_control_page(wxWindow *parent)
{
wxBoxSizer *bSizer_right = new wxBoxSizer(wxVERTICAL);
@ -3863,6 +3912,7 @@ void StatusPanel::on_camera_enter(wxMouseEvent& event)
}
sdcard_hint_dlg->on_show();
});
m_camera_popup->Bind(EVT_CAM_SOURCE_CHANGE, &StatusPanel::on_camera_source_change, this);
wxWindow* ctrl = (wxWindow*)event.GetEventObject();
wxPoint pos = ctrl->ClientToScreen(wxPoint(0, 0));
wxSize sz = ctrl->GetSize();
@ -3874,6 +3924,71 @@ void StatusPanel::on_camera_enter(wxMouseEvent& event)
}
}
void StatusBasePanel::on_camera_source_change(wxCommandEvent& event)
{
handle_camera_source_change();
}
void StatusBasePanel::handle_camera_source_change()
{
const auto new_cam_url = wxGetApp().app_config->get("camera", "custom_source");
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
if (enabled && !new_cam_url.empty()) {
m_custom_camera_view->LoadURL(new_cam_url);
toggle_custom_camera();
m_camera_switch_button->Show();
} else {
toggle_builtin_camera();
m_camera_switch_button->Hide();
}
}
void StatusBasePanel::toggle_builtin_camera()
{
m_custom_camera_view->Hide();
m_media_ctrl->Show();
m_media_play_ctrl->Show();
}
void StatusBasePanel::toggle_custom_camera()
{
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
if (enabled) {
m_custom_camera_view->Show();
m_media_ctrl->Hide();
m_media_play_ctrl->Hide();
}
}
void StatusBasePanel::on_camera_switch_toggled(wxMouseEvent& event)
{
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
if (enabled && m_media_ctrl->IsShown()) {
toggle_custom_camera();
} else {
toggle_builtin_camera();
}
}
void StatusBasePanel::remove_controls()
{
const std::string js_cleanup_video_element = R"(
document.body.style.overflow='hidden';
const video = document.querySelector('video');
video.setAttribute('style', 'width: 100% !important;');
video.removeAttribute('controls');
video.addEventListener('leavepictureinpicture', () => {
window.wx.postMessage('leavepictureinpicture');
});
video.addEventListener('enterpictureinpicture', () => {
window.wx.postMessage('enterpictureinpicture');
});
)";
m_custom_camera_view->RunScript(js_cleanup_video_element);
}
void StatusPanel::on_camera_leave(wxMouseEvent& event)
{
if (obj && m_camera_popup) {

View file

@ -287,6 +287,7 @@ protected:
ScalableBitmap m_bitmap_timelapse_off;
ScalableBitmap m_bitmap_vcamera_on;
ScalableBitmap m_bitmap_vcamera_off;
ScalableBitmap m_bitmap_switch_camera;
/* title panel */
wxPanel * media_ctrl_panel;
@ -307,6 +308,7 @@ protected:
wxStaticBitmap *m_bitmap_sdcard_img;
wxStaticBitmap *m_bitmap_static_use_time;
wxStaticBitmap *m_bitmap_static_use_weight;
wxStaticBitmap* m_camera_switch_button;
wxMediaCtrl2 * m_media_ctrl;
@ -326,6 +328,7 @@ protected:
ScalableButton *m_button_pause_resume;
ScalableButton *m_button_abort;
Button * m_button_clean;
wxWebView * m_custom_camera_view{nullptr};
wxStaticText * m_text_tasklist_caption;
@ -410,6 +413,13 @@ protected:
virtual void on_axis_ctrl_z_down_10(wxCommandEvent &event) { event.Skip(); }
virtual void on_axis_ctrl_e_up_10(wxCommandEvent &event) { event.Skip(); }
virtual void on_axis_ctrl_e_down_10(wxCommandEvent &event) { event.Skip(); }
void on_camera_source_change(wxCommandEvent& event);
void handle_camera_source_change();
void remove_controls();
void on_webview_navigating(wxWebViewEvent& evt);
void on_camera_switch_toggled(wxMouseEvent& event);
void toggle_custom_camera();
void toggle_builtin_camera();
public:
StatusBasePanel(wxWindow * parent,

View file

@ -1995,6 +1995,7 @@ void TabPrint::build()
optgroup->append_single_option_line("bridge_density");
optgroup->append_single_option_line("thick_bridges");
optgroup->append_single_option_line("thick_internal_bridges");
optgroup->append_single_option_line("dont_filter_internal_bridges");
optgroup = page->new_optgroup(L("Overhangs"), L"param_advanced");
optgroup->append_single_option_line("detect_overhang_wall");