Compare commits

..

2 Commits

Author SHA1 Message Date
ExPikaPaka
70bb888794 Demo 2026-07-03 09:31:58 +02:00
raistlin7447
adc8763099 fix: crash in Measure tool when a plain edge is the first selection (#14538)
* fix: crash in Measure tool when a plain edge is the first selection

The SPHERE_2 gripper raycaster called get_feature_offset() on
.first.feature instead of .second.feature (copy-pasted from the SPHERE_1
block). Plain planar-border edges store no extra point, so the Edge
branch dereferenced an empty optional behind a release-stripped assert,
aborting on Flatpak and undefined behavior elsewhere.

Point the SPHERE_2 raycaster at .second.feature and fall the Edge branch
back to the edge midpoint.

Fixes #14018
2026-07-03 11:29:28 +08:00
6 changed files with 23 additions and 527 deletions

238
README.md
View File

@@ -1,237 +1 @@
<div align="center">
<picture>
<img alt="OrcaSlicer logo" src="resources/images/OrcaSlicer.png" width="15%" height="15%">
</picture>
<a href="https://trendshift.io/repositories/15552" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15552" alt="OrcaSlicer%2FOrcaSlicer | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
[![GitHub Repo stars](https://img.shields.io/github/stars/OrcaSlicer/OrcaSlicer)](https://github.com/OrcaSlicer/OrcaSlicer/stargazers) [![Build all](https://github.com/OrcaSlicer/OrcaSlicer/actions/workflows/build_all.yml/badge.svg?branch=main)](https://github.com/OrcaSlicer/OrcaSlicer/actions/workflows/build_all.yml)
OrcaSlicer: an open source Next-Gen Slicing Software for Precision 3D Prints.
Optimize your prints with ultra-fast slicing, intelligent support generation, and seamless printer compatibility—engineered for perfection.
<h3>
# Official links and community
#### Official Website:
<a href="https://www.orcaslicer.com/" style="font-size:2em;">OrcaSlicer.com</a>
#### Github Repository:
<a href="https://github.com/OrcaSlicer/OrcaSlicer"><img src="https://img.shields.io/badge/OrcaSlicer-181717?style=flat&logo=github&logoColor=white" width="200" alt="GitHub Logo"/> </a>
#### Follow us:
<a href="https://twitter.com/real_OrcaSlicer"><img src="https://img.shields.io/badge/real__OrcaSlicer-000000?style=flat&logo=x&logoColor=white" width="200" alt="X Logo"/> </a>
<a href="https://www.youtube.com/@OfficialOrcaSlicer"><img src="https://img.shields.io/badge/OfficialOrcaSlicer-FF0000?style=flat&logo=youtube&logoColor=white" width="200" alt="YouTube Logo"/> </a>
#### Join our Discord community:
<a href="https://discord.gg/P4VE9UY9gJ"><img src="https://img.shields.io/badge/-Discord-5865F2?style=flat&logo=discord&logoColor=fff" width="200" alt="discord logo"/> </a>
<table border="2" style="border-color: #ffa500; background-color:rgb(232, 220, 180); color: #856404;">
<tr>
<td>
<strong>⚠️ CAUTION:</strong><br>
Several clickbait and malicious websites, such as <b>orca-slicer[.]com</b> and <b>orcaslicer[.]net</b>, are pretending to be the official OrcaSlicer site. These sites may redirect you to dangerous downloads or contain misleading information.<br>
<b>Our only official website is <a href="https://www.orcaslicer.com/">www.orcaslicer.com</a>.</b><br><br>
If you come across any of these in search results, please <b>report them</b> as unsafe or phishing to help keep the community secure with:<br>
- <a href="https://safebrowsing.google.com/safebrowsing/report_phish/">Google Safe Browsing</a><br>
- <a href="https://www.microsoft.com/en-us/wdsi/support/report-unsafe-site">Microsoft Security Intelligence</a><br>
- <a href="https://ipthreat.net/tools/reportphishing">IPThreat</a>
</td>
</tr>
</table>
</div>
# Main features
- **[Advanced Calibration Tools](https://www.orcaslicer.com/wiki/calibration_guide)**
Comprehensive suite: temperature towers, flow rate, retraction & more for optimal performance.
- **[Precise Wall](https://www.orcaslicer.com/wiki/quality_settings_precision#precise-wall) and [Seam Control](https://www.orcaslicer.com/wiki/quality_settings_seam)**
Adjust outer wall spacing and apply scarf seams to enhance print accuracy.
- **[Sandwich Mode](https://www.orcaslicer.com/wiki/quality_settings_wall_and_surfaces#innerouterinner) and [Polyholes](https://www.orcaslicer.com/wiki/quality_settings_precision#polyholes) Support**
Use varied infill [patterns](https://www.orcaslicer.com/wiki/strength_settings_patterns) and accurate hole shapes for improved clarity.
- **[Overhang](https://www.orcaslicer.com/wiki/quality_settings_overhangs) and [Support Optimization](https://www.orcaslicer.com/wiki#support-settings)**
Modify geometry for printable overhangs with precise support placement.
- **[Granular Controls and Customization](https://www.orcaslicer.com/wiki#process-settings)**
Fine-tune print speed, layer height, pressure, and temperature with precision.
- **Network Printer Support**
Seamless integration with Klipper, PrusaLink, and OctoPrint for remote control.
- **[Mouse Ear Brims](https://www.orcaslicer.com/wiki/others_settings_brim) & [Adaptive Bed Mesh](https://www.orcaslicer.com/wiki/printer_basic_information_adaptive_bed_mesh)**
Automatic brims and adaptive mesh calibration ensure consistent adhesion.
- **User-Friendly Interface**
Intuitive drag-and-drop design with pre-made profiles for popular printers.
- **[Open-Source](https://github.com/OrcaSlicer/OrcaSlicer) & [Community Driven](https://discord.gg/P4VE9UY9gJ)**
Regular updates fueled by continuous community contributions.
- **Wide Printer Compatibility**
Supports a broad range of printers: Bambu Lab, Prusa, Creality, Voron, and more.
- Additional features can be found in the [change notes](https://github.com/OrcaSlicer/OrcaSlicer/releases/).
# Wiki
The [wiki](https://www.orcaslicer.com/wiki) aims to provide a detailed explanation of the slicer settings, including how to maximize their use and how to calibrate and set up your printer.
- **[Access the wiki here](https://www.orcaslicer.com/wiki)**
- **[Contribute to the wiki](https://www.orcaslicer.com/wiki/how_to_wiki)**
# Download
## Stable Release
📥 **[Download the Latest Stable Release](https://github.com/OrcaSlicer/OrcaSlicer/releases/latest)**
Visit our GitHub Releases page for the latest stable version of OrcaSlicer, recommended for most users.
## Nightly Builds
🌙 **[Download the Latest Nightly Build](https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/nightly-builds)**
Explore the latest developments in OrcaSlicer with our nightly builds. Feedback on these versions is highly appreciated.
# How to install
## Windows
Download the **Windows Installer exe** for your preferred version from the [releases page](https://github.com/OrcaSlicer/OrcaSlicer/releases).
- *For convenience there is also a portable build available.*
<details>
<summary>Troubleshooting</summary>
- *If you have troubles to run the build, you might need to install following runtimes:*
- [MicrosoftEdgeWebView2RuntimeInstallerX64](https://github.com/OrcaSlicer/OrcaSlicer/releases/download/v1.0.10-sf2/MicrosoftEdgeWebView2RuntimeInstallerX64.exe)
- [Details of this runtime](https://aka.ms/webview2)
- [Alternative Download Link Hosted by Microsoft](https://go.microsoft.com/fwlink/p/?LinkId=2124703)
- [vcredist2019_x64](https://github.com/OrcaSlicer/OrcaSlicer/releases/download/v1.0.10-sf2/vcredist2019_x64.exe)
- [Alternative Download Link Hosted by Microsoft](https://aka.ms/vs/17/release/vc_redist.x64.exe)
- This file may already be available on your computer if you've installed visual studio. Check the following location: `%VCINSTALLDIR%Redist\MSVC\v142`
</details>
Windows Package Manager
```shell
winget install --id=SoftFever.OrcaSlicer -e
```
## Mac
1. Download the DMG for your computer: `arm64` version for Apple Silicon and `x86_64` for Intel CPU.
2. Drag OrcaSlicer.app to Application folder.
3. *If you want to run a build from a PR, you also need to follow the instructions below:*
<details>
<summary>Quarantine</summary>
- Option 1 (You only need to do this once. After that the app can be opened normally.):
- Step 1: Hold _cmd_ and right click the app, from the context menu choose **Open**.
- Step 2: A warning window will pop up, click _Open_
- Option 2:
Execute this command in terminal:
```shell
xattr -dr com.apple.quarantine /Applications/OrcaSlicer.app
```
- Option 3:
- Step 1: open the app, a warning window will pop up
![mac_cant_open](./SoftFever_doc/mac_cant_open.png)
- Step 2: in `System Settings` -> `Privacy & Security`, click `Open Anyway`:
![mac_security_setting](./SoftFever_doc/mac_security_setting.png)
</details>
## Linux
### Flathub (Recommended)
OrcaSlicer is available through FlatHub:
<a href='https://flathub.org/apps/com.orcaslicer.OrcaSlicer'><img width='240' alt='Download on Flathub' src='https://dl.flathub.org/assets/badges/flathub-badge-en.png'/></a>
Install from the command line:
```shell
flatpak install flathub com.orcaslicer.OrcaSlicer
flatpak run com.orcaslicer.OrcaSlicer
```
It can also be installed through graphical software managers (KDE Discover, GNOME Software, etc.) when Flathub is enabled. Search for **OrcaSlicer** in your software center.
### AppImage
AppImages are published for both **x86_64** and **aarch64** (ARM64). Pick the file matching your CPU — the ARM64 build has `aarch64` in its name (e.g. `OrcaSlicer_Linux_AppImage_Ubuntu2404_aarch64_*.AppImage`).
1. Download App image from the [releases page](https://github.com/OrcaSlicer/OrcaSlicer/releases).
2. Double click the downloaded file to run it.
3. If you run into trouble executing it, try this command in the terminal:
`chmod +x /path_to_appimage/OrcaSlicer_Linux.AppImage`
# How to Compile
All updated build instructions for Windows, macOS, and Linux are now available on the official [OrcaSlicer Wiki - How to build](https://www.orcaslicer.com/wiki/how_to_build) page.
Please refer to the wiki to ensure you're following the latest and most accurate steps for your platform.
# Klipper Note
If you're running Klipper, it's recommended to add the following configuration to your `printer.cfg` file.
```gcode
# Enable object exclusion
[exclude_object]
# Enable arcs support
[gcode_arcs]
resolution: 0.1
```
# Supports
**OrcaSlicer** is an open-source project and I'm deeply grateful to all my sponsors and backers.
Their generous support enables me to purchase filaments and other essential 3D printing materials for the project.
Thank you! :)
## Sponsors
<table>
<tr>
<td>
<a href="https://qidi3d.com/" style="display:inline-block; border-radius:8px; background:#fff;">
<img src="SoftFever_doc\sponsor_logos\QIDI.png" alt="QIDI" width="100" height="100">
</a>
</td>
<td>
<a href="https://bigtree-tech.com/" style="display:inline-block; border-radius:8px; background:#222;">
<img src="SoftFever_doc\sponsor_logos\BigTreeTech.png" alt="BIGTREE TECH" width="100" height="100">
</a>
</td>
</tr>
</table>
## Backers:
**Ko-fi supporters** ☕: [Backers list](https://github.com/user-attachments/files/16147016/Supporters_638561417699952499.csv)
## Support me
<a href="https://github.com/sponsors/SoftFever"><img src="https://img.shields.io/badge/GitHub%20Sponsors-30363D?style=flat&logo=GitHub-Sponsors&logoColor=EA4AAA" height="50"></a>
<a href="https://ko-fi.com/G2G5IP3CP"><img src="https://img.shields.io/badge/Support_me_on_Ko--fi-FF5E5B?style=flat&logo=ko-fi&logoColor=white" height="50"></a>
<a href="https://paypal.me/softfever3d"><img src="https://img.shields.io/badge/PayPal-003087?style=flat&logo=paypal&logoColor=fff" height="50"></a>
## Some Background
Open-source slicing has always been built on a tradition of collaboration and attribution. [Slic3r](https://github.com/Slic3r/Slic3r), created by Alessandro Ranellucci and the RepRap community, laid the foundation. [PrusaSlicer](https://github.com/prusa3d/PrusaSlicer) by Prusa Research built on Slic3r and acknowledged that heritage. [Bambu Studio](https://github.com/bambulab/BambuStudio) in turn forked from PrusaSlicer, and [SuperSlicer](https://github.com/supermerill/SuperSlicer) by @supermerill extended PrusaSlicer with community-driven enhancements. Each project carried the work of its predecessors forward, crediting those who came before.
OrcaSlicer began in that same spirit, drawing from BambuStudio, PrusaSlicer, and ideas inspired by CuraSlicer and SuperSlicer. But it has since grown far beyond its origins. Through relentless innovation — introducing advanced calibration tools, precise wall and seam control, tree supports, adaptive slicing, and hundreds of other features — OrcaSlicer has become the most widely used and actively developed open-source slicer in the 3D printing community. Many of its innovations have been adopted by other slicers, making it a driving force for the entire industry.
The OrcaSlicer logo was designed by community member [Justin Levine](https://github.com/jal-co).
# License
- **OrcaSlicer** is licensed under the GNU Affero General Public License, version 3.
- The **GNU Affero General Public License**, version 3 ensures that if you use any part of this software in any way (even behind a web server), your software must be released under the same license.
- OrcaSlicer includes a **pressure advance calibration pattern test** adapted from Andrew Ellis' generator, which is licensed under GNU General Public License, version 3. Ellis' generator is itself adapted from a generator developed by Sineos for Marlin, which is licensed under GNU General Public License, version 3.
- The **Bambu networking plugin** is based on non-free libraries from BambuLab. It is optional to the OrcaSlicer and provides extended functionalities for Bambulab printer users.
Remove this branch and PR later

View File

@@ -310,7 +310,6 @@ void GCodeProcessor::TimeMachine::reset()
g1_times_cache = std::vector<G1LinesCacheItem>();
first_layer_time = 0.0f;
prepare_time = 0.0f;
m_additional_time_buffer.clear();
}
static void planner_forward_pass_kernel(const GCodeProcessor::TimeBlock& prev, GCodeProcessor::TimeBlock& curr)
@@ -396,48 +395,13 @@ static void recalculate_trapezoids(std::vector<GCodeProcessor::TimeBlock>& block
}
}
GCodeProcessor::TimeMachine::AdditionalBuffer GCodeProcessor::TimeMachine::merge_adjacent_additional_time_blocks(const AdditionalBuffer& buffer)
void GCodeProcessor::TimeMachine::calculate_time(GCodeProcessorResult& result, PrintEstimatedStatistics::ETimeMode mode, size_t keep_last_n_blocks, float additional_time)
{
AdditionalBuffer merged;
if (buffer.empty())
return merged;
AdditionalBufferBlock current_block = buffer.front();
for (size_t idx = 1; idx < buffer.size(); ++idx) {
const AdditionalBufferBlock& next_block = buffer[idx];
if (current_block.first == next_block.first)
current_block.second += next_block.second;
else {
merged.push_back(current_block);
current_block = next_block;
}
}
merged.push_back(current_block);
return merged;
}
void GCodeProcessor::TimeMachine::calculate_time(GCodeProcessorResult& result, PrintEstimatedStatistics::ETimeMode mode, size_t keep_last_n_blocks, float additional_time, EMoveType target_move_type)
{
if (!enabled)
if (!enabled || blocks.size() < 2)
return;
if (blocks.size() < 2) {
// Not enough blocks to attribute the extra time to yet; buffer it so it is
// applied on a later pass instead of being dropped.
if (additional_time > 0.0f)
m_additional_time_buffer.emplace_back(target_move_type, additional_time);
return;
}
assert(keep_last_n_blocks <= blocks.size());
// Merge any previously buffered extra time with this call's extra time. Each
// entry is applied, in order, to the first block matching its target move type
// (EMoveType::Noop matches any block).
AdditionalBuffer additional_buffer = m_additional_time_buffer;
if (additional_time > 0.0f)
additional_buffer.emplace_back(target_move_type, additional_time);
additional_buffer = merge_adjacent_additional_time_blocks(additional_buffer);
// reverse_pass
for (int i = static_cast<int>(blocks.size()) - 1; i > 0; --i) {
planner_reverse_pass_kernel(blocks[i - 1], blocks[i]);
@@ -451,17 +415,11 @@ void GCodeProcessor::TimeMachine::calculate_time(GCodeProcessorResult& result, P
recalculate_trapezoids(blocks);
const size_t n_blocks_process = blocks.size() - keep_last_n_blocks;
size_t additional_buffer_idx = 0;
for (size_t i = 0; i < n_blocks_process; ++i) {
const TimeBlock& block = blocks[i];
float block_time = block.time();
if (additional_buffer_idx < additional_buffer.size()) {
const EMoveType buf_move_type = additional_buffer[additional_buffer_idx].first;
if (buf_move_type == EMoveType::Noop || buf_move_type == block.move_type) {
block_time += additional_buffer[additional_buffer_idx].second;
++additional_buffer_idx;
}
}
if (i == 0)
block_time += additional_time;
time += double(block_time);
result.moves[block.move_id].time[static_cast<size_t>(mode)] = block_time;
@@ -574,14 +532,6 @@ void GCodeProcessor::TimeMachine::calculate_time(GCodeProcessorResult& result, P
it_stop_time->elapsed_time = float(time);
}
// Carry forward any extra time that found no matching block this pass, so it
// is retried against the blocks of a later pass.
m_additional_time_buffer.clear();
if (additional_buffer_idx < additional_buffer.size())
m_additional_time_buffer.insert(m_additional_time_buffer.end(),
additional_buffer.begin() + additional_buffer_idx,
additional_buffer.end());
if (keep_last_n_blocks) {
blocks.erase(blocks.begin(), blocks.begin() + n_blocks_process);
@@ -5575,34 +5525,9 @@ void GCodeProcessor::process_filament_change(int id)
}
m_cp_color.current = m_extruder_colors[next_filament_id];
// Store the tool-change move first, then attribute the filament-change delay to
// it rather than to whichever motion block happens to be pending. This keeps the
// delay out of the per-role feature-time distribution (tool-change moves are not
// counted as an extrusion role) while still including it in the total and
// per-layer times. Mirrors BambuStudio's "separate flush time from other types"
// (c54a8333c7) and "unprocessed additional time" (27ef0b1bef) fixes.
simulate_st_synchronize(extra_time);
// store tool change move
store_move_vertex(EMoveType::Tool_change);
// Construct a zero-distance time block for the tool-change move on each enabled
// machine so the synchronize below can land the delay on it. simulate_st_synchronize
// flushes without keeping any blocks, so it is safe to append these directly.
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
TimeMachine& machine = m_time_processor.machines[i];
if (!machine.enabled)
continue;
TimeBlock block;
block.move_id = static_cast<unsigned int>(m_result.moves.size()) - 1;
block.move_type = EMoveType::Tool_change;
block.layer_id = std::max<unsigned int>(1, m_layer_id);
block.g1_line_id = m_g1_line_id;
block.flags.prepare_stage = m_processing_start_custom_gcode;
block.distance = 0.0f;
block.calculate_trapezoid();
machine.blocks.push_back(block);
}
simulate_st_synchronize(extra_time, EMoveType::Tool_change);
}
void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type, bool internal_only)
@@ -5926,13 +5851,13 @@ void GCodeProcessor::process_filaments(CustomGCode::Type code)
}
}
void GCodeProcessor::calculate_time(GCodeProcessorResult& result, size_t keep_last_n_blocks, float additional_time, EMoveType target_move_type)
void GCodeProcessor::calculate_time(GCodeProcessorResult& result, size_t keep_last_n_blocks, float additional_time)
{
// calculate times
std::vector<TimeMachine::ActualSpeedMove> actual_speed_moves;
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
TimeMachine& machine = m_time_processor.machines[i];
machine.calculate_time(m_result, static_cast<PrintEstimatedStatistics::ETimeMode>(i), keep_last_n_blocks, additional_time, target_move_type);
machine.calculate_time(m_result, static_cast<PrintEstimatedStatistics::ETimeMode>(i), keep_last_n_blocks, additional_time);
if (static_cast<PrintEstimatedStatistics::ETimeMode>(i) == PrintEstimatedStatistics::ETimeMode::Normal)
actual_speed_moves = std::move(machine.actual_speed_moves);
}
@@ -5986,9 +5911,9 @@ void GCodeProcessor::calculate_time(GCodeProcessorResult& result, size_t keep_la
}
}
void GCodeProcessor::simulate_st_synchronize(float additional_time, EMoveType target_move_type)
void GCodeProcessor::simulate_st_synchronize(float additional_time)
{
calculate_time(m_result, 0, additional_time, target_move_type);
calculate_time(m_result, 0, additional_time);
}
void GCodeProcessor::update_estimated_times_stats()

View File

@@ -560,21 +560,9 @@ class Print;
//BBS: prepare stage time before print model, including start gcode time and mostly same with start gcode time
float prepare_time;
// Orca: extra time (e.g. filament-change delay) that could not be attributed
// to a matching block on this pass is buffered here and applied on a later
// pass, so it is never dropped or folded into an unrelated move.
using AdditionalBufferBlock = std::pair<EMoveType, float>;
using AdditionalBuffer = std::vector<AdditionalBufferBlock>;
AdditionalBuffer m_additional_time_buffer;
void reset();
// Merge adjacent buffer entries that target the same move type.
static AdditionalBuffer merge_adjacent_additional_time_blocks(const AdditionalBuffer& buffer);
// additional_time is attributed to the first block matching target_move_type
// (EMoveType::Noop matches any block, i.e. the first processed block).
void calculate_time(GCodeProcessorResult& result, PrintEstimatedStatistics::ETimeMode mode, size_t keep_last_n_blocks = 0, float additional_time = 0.0f, EMoveType target_move_type = EMoveType::Noop);
void calculate_time(GCodeProcessorResult& result, PrintEstimatedStatistics::ETimeMode mode, size_t keep_last_n_blocks = 0, float additional_time = 0.0f);
};
struct UsedFilaments // filaments per ColorChange
@@ -1127,10 +1115,10 @@ class Print;
void process_custom_gcode_time(CustomGCode::Type code);
void process_filaments(CustomGCode::Type code);
void calculate_time(GCodeProcessorResult& result, size_t keep_last_n_blocks = 0, float additional_time = 0.0f, EMoveType target_move_type = EMoveType::Noop);
void calculate_time(GCodeProcessorResult& result, size_t keep_last_n_blocks = 0, float additional_time = 0.0f);
// Simulates firmware st_synchronize() call
void simulate_st_synchronize(float additional_time = 0.0f, EMoveType target_move_type = EMoveType::Noop);
void simulate_st_synchronize(float additional_time = 0.0f);
void update_estimated_times_stats();

View File

@@ -91,9 +91,14 @@ Vec3d GLGizmoMeasure::get_feature_offset(const Measure::SurfaceFeature &feature)
}
case Measure::SurfaceFeatureType::Edge:
{
std::optional<Vec3d> p = feature.get_extra_point();
assert(p.has_value());
ret = *p;
// Only polygon edges store an extra point (the polygon centre); plain edges have none.
const std::optional<Vec3d> extra = feature.get_extra_point();
if (extra.has_value())
ret = *extra;
else {
const auto [pt1, pt2] = feature.get_edge();
ret = 0.5 * (pt1 + pt2);
}
break;
}
case Measure::SurfaceFeatureType::Point:
@@ -1065,7 +1070,7 @@ void GLGizmoMeasure::on_render()
if (requires_raycaster_update) {
if (m_gripper_id_raycast_map.find(GripperType::SPHERE_2) != m_gripper_id_raycast_map.end()) {
m_gripper_id_raycast_map[GripperType::SPHERE_2]->set_transform(Geometry::translation_transform(get_feature_offset(*m_selected_features.first.feature)) *
m_gripper_id_raycast_map[GripperType::SPHERE_2]->set_transform(Geometry::translation_transform(get_feature_offset(*m_selected_features.second.feature)) *
Geometry::scale_transform(inv_zoom));
}
}

View File

@@ -7,7 +7,6 @@ add_executable(${_TEST_NAME}_tests
test_fill.cpp
test_flow.cpp
test_gcode.cpp
test_gcode_timing.cpp
test_gcodewriter.cpp
test_model.cpp
test_print.cpp

View File

@@ -1,185 +0,0 @@
#include <catch2/catch_all.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "test_utils.hpp"
#include <fstream>
#include <map>
using namespace Slic3r;
using Catch::Matchers::WithinAbs;
// Regression coverage for filament/tool-change time being folded into the first
// pending motion block (an extrusion move) instead of the tool-change move, and
// for that delay being dropped entirely when too few motion blocks precede the
// change. See BambuStudio "seperate flush time from other types" (c54a8333c7)
// and the follow-up "unprocessed addtional time" fix (27ef0b1bef).
namespace {
constexpr size_t NORMAL = static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal);
FullPrintConfig make_config(double load_time, double unload_time, double tool_change_time)
{
FullPrintConfig config; // default-initialized with the built-in defaults
config.gcode_flavor.value = gcfMarlinFirmware;
// Two filaments, both assigned to the same (single) extruder, so a T1 after
// T0 is a same-extruder filament swap that costs unload + load time.
config.filament_diameter.values = {1.75, 1.75};
config.filament_map.values = {1, 1};
config.machine_load_filament_time.value = load_time;
config.machine_unload_filament_time.value = unload_time;
config.machine_tool_change_time.value = tool_change_time;
return config;
}
void run_processor(GCodeProcessor& proc, const FullPrintConfig& config, const char* gcode)
{
// reserved_tag() selects between two tag tables based on this shared static, and
// other tests in the binary mutate it -- pin it so our "; FEATURE:" role tags are
// parsed deterministically regardless of test execution order.
GCodeProcessor::s_IsBBLPrinter = true;
ScopedTemporaryFile temp(".gcode");
{
std::ofstream os(temp.string());
os << gcode;
}
proc.apply_config(config);
// No producer marker in the gcode, so process_file keeps our applied config.
proc.process_file(temp.string());
}
// Estimated time per extrusion role, grouped exactly the way libvgcode builds the
// feature-type legend: sum MoveVertex.time over EMoveType::Extrude moves keyed by
// extrusion_role (see ViewerImpl.cpp:1017 -- only Extrude moves are counted).
std::map<ExtrusionRole, double> role_times(const GCodeProcessorResult& r)
{
std::map<ExtrusionRole, double> m;
for (const auto& mv : r.moves)
if (mv.type == EMoveType::Extrude)
m[mv.extrusion_role] += mv.time[NORMAL];
return m;
}
// Sum of estimated time attributed to tool-change moves.
double sum_tool_change_time(const GCodeProcessorResult& r)
{
double t = 0.0;
for (const auto& mv : r.moves)
if (mv.type == EMoveType::Tool_change)
t += mv.time[NORMAL];
return t;
}
// Total filament-change delay, accumulated independently of the timing machinery.
double filament_change_delay(const GCodeProcessorResult& r)
{
const auto& s = r.print_statistics;
return s.total_filament_load_time + s.total_filament_unload_time + s.total_tool_change_time;
}
} // namespace
TEST_CASE("Filament-change time is attributed to tool-change moves, not extrusion roles", "[GCodeTiming]")
{
// Relative extrusion (M83) so every "E5" is a real 5mm extrusion move rather
// than a zero-delta travel. Two real travels precede T0 so its delay is flushed
// cleanly. The extrusions after T0 span several roles (Outer wall, Sparse infill,
// Inner wall); the first pending block at T1 is an "Outer wall" move, so the
// buggy code folds the T1 delay into that role. The per-role check below verifies
// EVERY role stays clean, not just one, and catches any role-to-role misattribution.
const char* gcode =
"M83\n"
"; FEATURE: Outer wall\n"
"G1 X10 Y10 Z0.2 F600\n"
"G1 X0 Y0 F6000\n"
"T0\n"
"; FEATURE: Outer wall\n"
"G1 X50 Y0 E5 F1800\n"
"G1 X50 Y50 E5\n"
"; FEATURE: Sparse infill\n"
"G1 X0 Y50 E5\n"
"G1 X0 Y0 E5\n"
"T1\n"
"; FEATURE: Inner wall\n"
"G1 X50 Y0 E5\n"
"G1 X50 Y50 E5\n";
GCodeProcessor proc_zero;
run_processor(proc_zero, make_config(0.0, 0.0, 0.0), gcode);
const GCodeProcessorResult& r_zero = proc_zero.get_result();
const double load = 10.0;
const double unload = 5.0;
GCodeProcessor proc_delay;
run_processor(proc_delay, make_config(load, unload, 0.0), gcode);
const GCodeProcessorResult& r_delay = proc_delay.get_result();
const double delay = filament_change_delay(r_delay);
// Preconditions: the filament changes were charged, and cost nothing in the
// zero-time baseline.
REQUIRE(delay > 0.0);
REQUIRE_THAT(filament_change_delay(r_zero), WithinAbs(0.0, 1e-9));
// The delay must not inflate the time of ANY extrusion role. Compare the full
// per-role breakdown (exactly how the feature-type legend is built) between the
// zero-delay and delayed runs -- every role must match to within tolerance.
const auto roles_zero = role_times(r_zero);
const auto roles_delay = role_times(r_delay);
// Guard: the gcode must genuinely exercise multiple distinct roles (Outer wall,
// Sparse infill, Inner wall), otherwise this check would silently cover only one.
REQUIRE(roles_zero.size() >= 3);
REQUIRE(roles_zero.size() == roles_delay.size());
for (const auto& [role, zero_time] : roles_zero) {
INFO("extrusion role index = " << static_cast<int>(role));
REQUIRE(roles_delay.count(role) == 1);
REQUIRE_THAT(roles_delay.at(role), WithinAbs(zero_time, 1e-2));
}
// The delay must instead land on the tool-change moves, so per-move consumers
// (layer-time view, layer slider) stay consistent.
REQUIRE_THAT(sum_tool_change_time(r_delay), WithinAbs(delay, 1e-2));
// Both tool changes occur on layer 1, so the delay must also be reflected in
// the first-layer time.
const double first_layer_delta = proc_delay.get_first_layer_time(PrintEstimatedStatistics::ETimeMode::Normal)
- proc_zero.get_first_layer_time(PrintEstimatedStatistics::ETimeMode::Normal);
REQUIRE_THAT(first_layer_delta, WithinAbs(delay, 1e-2));
}
TEST_CASE("Filament-change time is not dropped when few motion blocks precede the change", "[GCodeTiming]")
{
// Only a single motion block precedes T0, so the buggy code's "fewer than two
// pending blocks" early-out discards that filament-change delay entirely,
// making the total print time inconsistent with the reported statistics.
const char* gcode =
"; FEATURE: Outer wall\n"
"G1 X10 Y10 Z0.2 F600\n"
"T0\n"
"G1 X50 Y0 E5 F1800\n"
"G1 X50 Y50 E5\n"
"T1\n"
"G1 X0 Y50 E5\n"
"G1 X0 Y0 E5\n";
GCodeProcessor proc_zero;
run_processor(proc_zero, make_config(0.0, 0.0, 0.0), gcode);
const double load = 10.0;
const double unload = 5.0;
GCodeProcessor proc_delay;
run_processor(proc_delay, make_config(load, unload, 0.0), gcode);
const GCodeProcessorResult& r_delay = proc_delay.get_result();
const double delay = filament_change_delay(r_delay);
REQUIRE(delay > 0.0);
// Every second of reported filament-change delay must be present in the total
// estimated print time; none may be silently dropped.
const double total_delta = proc_delay.get_time(PrintEstimatedStatistics::ETimeMode::Normal)
- proc_zero.get_time(PrintEstimatedStatistics::ETimeMode::Normal);
REQUIRE_THAT(total_delta, WithinAbs(delay, 1e-2));
}