mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 22:42:37 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0410db22e | ||
|
|
8eac7aafdb | ||
|
|
f956914a11 | ||
|
|
f9e9cff53a | ||
|
|
8a17df4a89 |
@@ -330,12 +330,13 @@ jobs:
|
||||
# sources are unchanged, so a re-run of the same commit would skip the
|
||||
# OrcaSlicer module and ship no test asset. A per-run value in that module's
|
||||
# env keeps it rebuilding; orca_deps stays cached, and the compiler cache
|
||||
# still serves the rebuild.
|
||||
- name: Inject commit hash and flatpak-builder cache buster into Flatpak manifest
|
||||
# still serves the rebuild. run-tests on the same module builds the test
|
||||
# binaries.
|
||||
- name: Inject commit hash, run-tests and cache buster into Flatpak manifest
|
||||
env:
|
||||
flatpak_builder_cache_buster: ${{ github.run_id }}-${{ github.run_attempt }}
|
||||
run: |
|
||||
sed -i "/name: OrcaSlicer/{n;s|buildsystem: simple|buildsystem: simple\n build-options:\n env:\n flatpak_builder_cache_buster: \"$flatpak_builder_cache_buster\"\n git_commit_hash: \"$git_commit_hash\"|}" \
|
||||
sed -i "/name: OrcaSlicer/{n;s|buildsystem: simple|buildsystem: simple\n run-tests: true\n build-options:\n env:\n flatpak_builder_cache_buster: \"$flatpak_builder_cache_buster\"\n git_commit_hash: \"$git_commit_hash\"|}" \
|
||||
scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
|
||||
shell: bash
|
||||
# flatpak-builder's --ccache only wraps cc and gcc, and the manifest builds
|
||||
@@ -381,9 +382,6 @@ jobs:
|
||||
save-cache: false
|
||||
arch: ${{ matrix.variant.arch }}
|
||||
upload-artifact: false
|
||||
# run-tests fires the module's build-only test-commands; keep-build-dirs
|
||||
# retains the binaries for the packaging step below.
|
||||
run-tests: true
|
||||
keep-build-dirs: true
|
||||
# The build has just touched everything it can use, so an object untouched
|
||||
# for a week is dead, usually orphaned by a flag change.
|
||||
|
||||
@@ -16408,7 +16408,7 @@ msgid "Min print speed"
|
||||
msgstr "Minimale Druckgeschwindigkeit"
|
||||
|
||||
msgid "The minimum print speed to which the printer slows down to maintain the minimum layer time defined above when the slowdown for better layer cooling is enabled."
|
||||
msgstr "Das minimale Drucktempo, bei dem der Drucker verlangsamt wird, um die oben definierte minimale Schichtzeit einzuhalten, wenn die Verlangsamung für eine bessere Schichtkühlung aktiviert ist."
|
||||
msgstr "Die minimale Druckgeschwindigkeit, auf die der Drucker verlangsamt wird, um die oben definierte minimale Schichtzeit einzuhalten, wenn die Verlangsamung für eine bessere Schichtkühlung aktiviert ist."
|
||||
|
||||
msgid "The diameter of nozzle."
|
||||
msgstr "Düsendurchmesser"
|
||||
|
||||
@@ -384,7 +384,7 @@ modules:
|
||||
- cmake --build build_flatpak --target generate_system_cache -j$FLATPAK_BUILDER_N_JOBS
|
||||
- ./scripts/build_preset_cache.sh -n -b build_flatpak /app/share/OrcaSlicer/profiles
|
||||
|
||||
# Built (not run) here via the action's run-tests, then shipped to a separate
|
||||
# Built (not run) here when CI injects run-tests, then shipped to a separate
|
||||
# test job. Only the test sources compile; nothing installs to /app.
|
||||
test-commands:
|
||||
- cmake . -B build_flatpak -DBUILD_TESTS=ON
|
||||
|
||||
@@ -776,7 +776,9 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati
|
||||
{
|
||||
// Get stored option value.
|
||||
const ConfigOption *raw_opt = this->option(opt_key);
|
||||
assert(raw_opt != nullptr);
|
||||
// Mirror the single-arg overload — assert() is a no-op under NDEBUG.
|
||||
if (raw_opt == nullptr)
|
||||
throw ConfigurationError("ConfigBase::get_abs_value(): \"" + opt_key + "\" is not defined");
|
||||
if (raw_opt->type() != coFloatOrPercent)
|
||||
throw ConfigurationError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
|
||||
// Compute absolute value.
|
||||
|
||||
@@ -120,9 +120,6 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
|
||||
fill_no_overlap
|
||||
);
|
||||
|
||||
if (this->layer()->lower_layer != nullptr)
|
||||
// Cummulative sum of polygons over all the regions.
|
||||
g.lower_slices = &this->layer()->lower_layer->lslices;
|
||||
if (this->layer()->upper_layer != NULL)
|
||||
g.upper_slices = &this->layer()->upper_layer->lslices;
|
||||
|
||||
@@ -135,6 +132,13 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
|
||||
g.overhang_flow = this->bridging_flow(frPerimeter, object_config.thick_bridges);
|
||||
g.solid_infill_flow = this->flow(frSolidInfill);
|
||||
|
||||
// Cumulative sum of polygons over all the regions, less what the lower layer could not print.
|
||||
ExPolygons lower_slices;
|
||||
if (this->layer()->lower_layer != nullptr) {
|
||||
lower_slices = g.printable_slices(this->layer()->lower_layer->lslices);
|
||||
g.lower_slices = &lower_slices;
|
||||
}
|
||||
|
||||
if (this->layer()->object()->config().wall_generator.value == PerimeterGeneratorType::Arachne && !spiral_mode)
|
||||
g.process_arachne();
|
||||
else
|
||||
|
||||
@@ -2885,6 +2885,21 @@ bool PerimeterGeneratorLoop::is_internal_contour() const
|
||||
return true;
|
||||
}
|
||||
|
||||
// ORCA: Arachne drops features below min_feature_size, classic builds nothing thinner than a third of the
|
||||
// nozzle. Both describe the layer below, a union of regions sharing neither nozzle nor generator, so every
|
||||
// ambiguity resolves low: it may keep a sliver that was never printed, but it never drops one that was.
|
||||
ExPolygons PerimeterGenerator::printable_slices(const ExPolygons &slices) const
|
||||
{
|
||||
double min_width = *std::min_element(print_config->nozzle_diameter.values.begin(),
|
||||
print_config->nozzle_diameter.values.end()) / 3.;
|
||||
if (object_config->wall_generator.value == PerimeterGeneratorType::Arachne) {
|
||||
const double min_feature_size = Arachne::make_paths_params(layer_id, *object_config, *print_config).min_feature_size;
|
||||
// Spiral vase can put a classic layer under an Arachne one, so there both limits apply.
|
||||
min_width = print_config->spiral_mode ? std::min(min_width, min_feature_size) : min_feature_size;
|
||||
}
|
||||
return min_width > EPSILON ? opening_ex(slices, float(scale_(min_width / 2.))) : slices;
|
||||
}
|
||||
|
||||
std::vector<Polygons> PerimeterGenerator::generate_lower_polygons_series(float width)
|
||||
{
|
||||
float nozzle_diameter = print_config->nozzle_diameter.get_at(config->outer_wall_filament_id - 1);
|
||||
|
||||
@@ -149,6 +149,8 @@ public:
|
||||
//BBS
|
||||
double smaller_width_ext_mm3_per_mm() const { return m_ext_mm3_per_mm_smaller_width; }
|
||||
Polygons lower_slices_polygons() const { return m_lower_slices_polygons; }
|
||||
// ORCA: the slices less the slivers the wall generator prints nothing for, so they never count as support.
|
||||
ExPolygons printable_slices(const ExPolygons &slices) const;
|
||||
|
||||
private:
|
||||
std::vector<Polygons> generate_lower_polygons_series(float width);
|
||||
|
||||
+14
-3
@@ -11,12 +11,23 @@ namespace Slic3r {
|
||||
float CalibPressureAdvance::find_optimal_PA_speed(const DynamicPrintConfig &config, double line_width, double layer_height, int extruder_id, int filament_idx)
|
||||
{
|
||||
const double general_suggested_min_speed = 100.0;
|
||||
double filament_max_volumetric_speed = config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->get_at(filament_idx);
|
||||
// Read defensively — CLI callers may hand us a config missing optional keys.
|
||||
auto vector_at = [&config](const char *key, int idx) -> double {
|
||||
if (const auto *o = config.option<ConfigOptionFloats>(key)) return o->get_at(idx);
|
||||
const ConfigOptionDef *d = config.def() ? config.def()->get(key) : nullptr;
|
||||
return (d && d->default_value) ? d->get_default_value<ConfigOptionFloats>()->get_at(idx) : 0.0;
|
||||
};
|
||||
auto nullable_at = [&config](const char *key, int idx) -> double {
|
||||
if (const auto *o = config.option<ConfigOptionFloatsNullable>(key)) return o->get_at(idx);
|
||||
const ConfigOptionDef *d = config.def() ? config.def()->get(key) : nullptr;
|
||||
return (d && d->default_value) ? d->get_default_value<ConfigOptionFloatsNullable>()->get_at(idx) : 0.0;
|
||||
};
|
||||
double filament_max_volumetric_speed = vector_at("filament_max_volumetric_speed", filament_idx);
|
||||
// todo multi_extruders:
|
||||
const float nozzle_diameter = config.option<ConfigOptionFloats>("nozzle_diameter")->get_at(extruder_id);
|
||||
const float nozzle_diameter = vector_at("nozzle_diameter", extruder_id);
|
||||
if (line_width <= 0.) line_width = Flow::auto_extrusion_width(frPerimeter, nozzle_diameter);
|
||||
Flow pattern_line = Flow(line_width, layer_height, nozzle_diameter);
|
||||
auto pa_speed = std::min(std::max(general_suggested_min_speed, config.option<ConfigOptionFloatsNullable>("outer_wall_speed")->get_at(extruder_id)),
|
||||
auto pa_speed = std::min(std::max(general_suggested_min_speed, nullable_at("outer_wall_speed", extruder_id)),
|
||||
filament_max_volumetric_speed / pattern_line.mm3_per_mm());
|
||||
|
||||
return std::floor(pa_speed);
|
||||
|
||||
@@ -234,8 +234,10 @@ bool GLGizmosManager::init()
|
||||
#ifdef SLIC3R_CAD
|
||||
// Registered last: Primitive and Sketch are the final entries before Undefined, so
|
||||
// omitting them leaves every preceding m_gizmos index (indexed by EType) untouched.
|
||||
m_gizmos.emplace_back(new GLGizmoPrimitive(m_parent, m_is_dark ? "toolbar_modifier_cube_dark.svg" : "toolbar_modifier_cube.svg", static_cast<unsigned int>(Primitive)));
|
||||
m_gizmos.emplace_back(new GLGizmoSketch(m_parent, m_is_dark ? "toolbar_sketch_dark.svg" : "toolbar_sketch.svg", static_cast<unsigned int>(Sketch)));
|
||||
if (wxGetApp().is_enable_cad_feature()) {
|
||||
m_gizmos.emplace_back(new GLGizmoPrimitive(m_parent, m_is_dark ? "toolbar_modifier_cube_dark.svg" : "toolbar_modifier_cube.svg", static_cast<unsigned int>(Primitive)));
|
||||
m_gizmos.emplace_back(new GLGizmoSketch(m_parent, m_is_dark ? "toolbar_sketch_dark.svg" : "toolbar_sketch.svg", static_cast<unsigned int>(Sketch)));
|
||||
}
|
||||
#endif
|
||||
//m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", sprite_id++));
|
||||
//m_gizmos.emplace_back(new GLGizmoFaceDetector(m_parent, "face recognition.svg", sprite_id++));
|
||||
@@ -1344,7 +1346,8 @@ GLGizmoBase* GLGizmosManager::get_current() const
|
||||
|
||||
GLGizmoBase* GLGizmosManager::get_gizmo(GLGizmosManager::EType type) const
|
||||
{
|
||||
return ((type == Undefined) || m_gizmos.empty()) ? nullptr : m_gizmos[type].get();
|
||||
// m_gizmos ends before the enum does when the CAD gizmos are not registered.
|
||||
return type < m_gizmos.size() ? m_gizmos[type].get() : nullptr;
|
||||
}
|
||||
|
||||
GLGizmosManager::EType GLGizmosManager::get_gizmo_from_name(const std::string& gizmo_name) const
|
||||
|
||||
@@ -495,3 +495,138 @@ TEST_CASE("Loops waiting for the infill are extruded after it", "[Perimeters]")
|
||||
CHECK(ceiling_roles(true) == std::vector<std::string>{ "perimeter", "infill", "perimeter" });
|
||||
CHECK(ceiling_roles(false) == std::vector<std::string>{ "perimeter", "infill" });
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// The rib spans z=[0,5] and the slab z=[5,6], so this is the slab's first layer - the only one whose
|
||||
// support comes from the rib rather than from the slab below it.
|
||||
const double slab_first_layer_z = 5.2;
|
||||
|
||||
// Rib widths either side of what the wall generators can print. At a 0.4mm nozzle the classic generator
|
||||
// builds nothing thinner than nozzle/3 = 0.133mm and Arachne drops anything below min_feature_size, 25%
|
||||
// of the nozzle = 0.1mm. 0.08mm is under both thresholds, 0.3mm over both.
|
||||
const double unprintable_rib = 0.08;
|
||||
const double printable_rib = 0.3;
|
||||
|
||||
// A 4x5mm anchor tower carrying a 20x5mm slab at z=[5,6], with a rib `rib_width` wide running the whole
|
||||
// length of the slab beneath its y=0 edge; a `rib_width` of 0 leaves the rib out. Nothing else is under
|
||||
// that edge, so whether the wall along it is an overhang rests entirely on the rib. Overhang detection
|
||||
// grows the lower slices by half the nozzle diameter before it asks, which carries either rib past the
|
||||
// 0.21mm from the slab edge to that wall - the unprintable one only fails to reach it once it is filtered
|
||||
// out for being unprintable.
|
||||
Print &slab_over_rib(Print &print, Model &model, double rib_width, const DynamicPrintConfig &config)
|
||||
{
|
||||
ModelObject *object = model.add_object();
|
||||
object->name = "slab_over_rib.stl";
|
||||
object->add_volume(make_cube(4., 5., 6.), ModelVolumeType::MODEL_PART, false);
|
||||
if (rib_width > 0.) {
|
||||
TriangleMesh rib = make_cube(20., rib_width, 5.);
|
||||
rib.translate(4.f, 0.f, 0.f);
|
||||
object->add_volume(std::move(rib), ModelVolumeType::MODEL_PART, false);
|
||||
}
|
||||
TriangleMesh slab = make_cube(20., 5., 1.);
|
||||
slab.translate(4.f, 0.f, 5.f);
|
||||
object->add_volume(std::move(slab), ModelVolumeType::MODEL_PART, false);
|
||||
object->add_instance();
|
||||
object->ensure_on_bed();
|
||||
|
||||
print.auto_assign_extruders(object);
|
||||
print.apply(model, config);
|
||||
print.validate();
|
||||
print.set_status_silent();
|
||||
return print;
|
||||
}
|
||||
|
||||
// Every setting the assertions below depend on, so none of them rests on a default. The wall line widths
|
||||
// are pinned because the rib widths above are chosen against the distance from the slab edge to its outer
|
||||
// wall, and min_feature_size because it is one of the two thresholds under test.
|
||||
DynamicPrintConfig printable_rib_config(const char *wall_generator, bool detect_thin_wall)
|
||||
{
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "wall_generator", wall_generator },
|
||||
{ "layer_height", 0.2 }, // puts a layer boundary exactly on the top of the rib
|
||||
{ "initial_layer_print_height", 0.2 },
|
||||
{ "nozzle_diameter", "0.4" },
|
||||
{ "outer_wall_line_width", 0.42 },
|
||||
{ "inner_wall_line_width", 0.45 },
|
||||
{ "wall_loops", 2 },
|
||||
{ "detect_overhang_wall", true },
|
||||
{ "detect_thin_wall", detect_thin_wall },
|
||||
{ "min_feature_size", "25%" },
|
||||
{ "raft_layers", 0 },
|
||||
// Anything that adds, drops or reorders walls would move length between the roles being counted.
|
||||
{ "extra_perimeters_on_overhangs", false },
|
||||
{ "overhang_reverse", false },
|
||||
{ "only_one_wall_top", false },
|
||||
{ "only_one_wall_first_layer", false },
|
||||
{ "unsupported_wall_last", false },
|
||||
{ "sparse_infill_density", "15%" },
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
// Length of every overhang perimeter path on the layer at `print_z`, loops and open extrusions alike.
|
||||
double overhang_length_at(const Print &print, double print_z)
|
||||
{
|
||||
double len = 0.;
|
||||
const auto add_entity = [&len](const ExtrusionEntity *entity, auto &&self) -> void {
|
||||
const auto add_paths = [&len](const ExtrusionPaths &paths) {
|
||||
for (const ExtrusionPath &path : paths)
|
||||
if (path.role() == erOverhangPerimeter)
|
||||
len += path.length();
|
||||
};
|
||||
if (const auto *coll = dynamic_cast<const ExtrusionEntityCollection*>(entity)) {
|
||||
for (const ExtrusionEntity *child : coll->entities)
|
||||
self(child, self);
|
||||
} else if (const auto *loop = dynamic_cast<const ExtrusionLoop*>(entity)) {
|
||||
add_paths(loop->paths);
|
||||
} else if (const auto *multi = dynamic_cast<const ExtrusionMultiPath*>(entity)) {
|
||||
add_paths(multi->paths);
|
||||
} else if (const auto *path = dynamic_cast<const ExtrusionPath*>(entity)) {
|
||||
if (path->role() == erOverhangPerimeter)
|
||||
len += path->length();
|
||||
}
|
||||
};
|
||||
|
||||
for (const Layer *layer : print.objects().front()->layers()) {
|
||||
if (std::abs(layer->print_z - print_z) > EPSILON)
|
||||
continue;
|
||||
for (const LayerRegion *region : layer->regions())
|
||||
add_entity(®ion->perimeters, add_entity);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// A sliver the wall generator prints nothing for holds nothing up, so it cannot be what decides that the
|
||||
// wall above it is not an overhang. The rib under the slab is the only thing that edge of the slab could
|
||||
// rest on: below the threshold of the active generator the slab has to come out exactly as it does with
|
||||
// no rib at all, and the last check is the control - a rib the generator does print anchors that wall,
|
||||
// without which the first check would hold for want of any sensitivity to the rib.
|
||||
TEST_CASE("A lower layer sliver too thin to print does not support the wall above it", "[Perimeters]")
|
||||
{
|
||||
const char *wall_generator = GENERATE("classic", "arachne");
|
||||
const bool detect_thin_wall = GENERATE(true, false);
|
||||
CAPTURE(wall_generator, detect_thin_wall);
|
||||
|
||||
auto overhang_for = [wall_generator, detect_thin_wall](double rib_width) {
|
||||
Print print;
|
||||
Model model;
|
||||
slab_over_rib(print, model, rib_width, printable_rib_config(wall_generator, detect_thin_wall));
|
||||
print.process();
|
||||
REQUIRE_FALSE(print.objects().empty());
|
||||
return overhang_length_at(print, slab_first_layer_z);
|
||||
};
|
||||
|
||||
const double no_rib = overhang_for(0.);
|
||||
const double unprintable = overhang_for(unprintable_rib);
|
||||
const double printable = overhang_for(printable_rib);
|
||||
|
||||
// Only where the slab meets the tower is it held up from below, so both of its 20mm walls overhang.
|
||||
REQUIRE(no_rib > scale_(30.));
|
||||
CHECK_THAT(unprintable, Catch::Matchers::WithinAbs(no_rib, scale_(1.)));
|
||||
// A rib that does get printed takes the 20mm outer wall running along it out of the overhangs.
|
||||
CHECK(printable < no_rib - scale_(15.));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user