Merge branch 'main' into fix/plugin-bugs

This commit is contained in:
Ian Chua
2026-07-22 13:14:03 +08:00
committed by GitHub
25 changed files with 3557 additions and 84 deletions

View File

@@ -0,0 +1,88 @@
# Compare Analyzer — G-code Slicing Comparison Tools
Tools for deep comparison and analysis of `.3mf` slicing project files, designed for
verifying multi-nozzle (H2C carousel) and multi-extruder slicing correctness.
## Tools
### `compare_slices.py` — Slice Comparison Analyzer
Deep comparison of two `.3mf` files (OrcaSlicer, BambuStudio, or any compatible slicer).
Generates a comprehensive Markdown report covering:
- **Filament usage** — per-filament weight/length with color mapping
- **Nozzle/extruder mapping** — Vortek carousel slot assignments
- **Tool change sequences** — T-code ordering and count
- **Prime tower analysis** — tower entries, G-code line count
- **Temperature timeline** — pre-heat lead times, target temperatures per tool change
- **Retract parameters** — M620.11 analysis during nozzle switches
- **Filament change G-code blocks** — line-by-line diff of change_filament_gcode
- **Control command diff** — timeline of M/G-code differences
- **Critical discrepancy detection** — automatic flagging of weight/time anomalies
#### Usage
```bash
# Compare two slice files
python3 compare_slices.py file1.3mf file2.3mf
# With custom labels
python3 compare_slices.py file1.3mf file2.3mf --labels "Upstream" "Fixed"
```
#### Output
Markdown report saved to `mp_reports/compare_report_YYYYMMDD_HHMMSS.md`
#### Example: Detecting H2C purge regression
```
⚠️ CRITICAL DISCREPANCY: Huge difference in part weight:
OrcaSlicer 60.90 g vs BambuStudio 17.47 g (difference 43.43 g or 71.3%).
The reason is incorrect nozzle mapping, causing huge AMS flushing.
```
---
### `show_temp_plot.py` — Temperature Timeline Plotter
Generates interactive HTML temperature plots for analyzing thermal profiles during
multi-nozzle prints. Visualizes heater temperature commands (M104/M109) per tool change,
showing pre-heat timing and temperature convergence.
#### Architecture
- H2C dual-extruder layout with Vortek carousel nozzles
- Physical heaters mapped dynamically:
- Heater 0: Extruder 2 (right nozzle slot, T0/T2/T3/T4)
- Heater 1: Extruder 1 (left nozzle slot, T1)
- Active heater mapping derived from G-code temperature signals
#### Usage
```bash
# Single file analysis
python3 show_temp_plot.py file.3mf
# Side-by-side comparison of two files
python3 show_temp_plot.py file1.3mf file2.3mf
```
#### Output
Interactive HTML report saved to Desktop as `temp_plot_v3.html`
---
## Requirements
- **Python 3.8+**
- **No external dependencies** — uses only Python standard library
(`json`, `zipfile`, `xml.etree.ElementTree`, `difflib`, `webbrowser`)
## Use Cases
1. **Regression testing** — compare slices before/after code changes to verify
no unintended differences in purge volumes, tool ordering, or temperature timing
2. **BBS compatibility verification** — compare OrcaSlicer output against BambuStudio
reference slices to ensure behavioral parity
3. **H2C carousel validation** — verify per-slot nozzle tracking produces correct
purge volumes (not collapsed per-extruder)
4. **Temperature protocol analysis** — verify pre-heat lead times and cooling
temperatures during nozzle changes match expected profiles

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,9 @@
#include <catch2/catch_all.hpp>
#include <algorithm>
#include <sstream>
#include <string>
#include "libslic3r/calib.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/TriangleMesh.hpp"
@@ -38,3 +42,69 @@ TEST_CASE("Zero calibration line width resolves to a positive default", "[Calib]
REQUIRE(pattern.line_width() > 0.);
REQUIRE(pattern.line_width_first_layer() > 0.);
}
namespace {
struct EndState { double final_e; double max_e; };
EndState simulate_absolute_e(const std::string &gcode)
{
double final_e = 0.;
double max_e = 0.;
std::istringstream lines(gcode);
std::string line;
while (std::getline(lines, line)) {
std::istringstream words(line);
std::string op;
if (!(words >> op))
continue;
if (op != "G1" && op != "G0" && op != "G92")
continue;
std::string word;
while (words >> word) {
if (word.size() >= 2 && word[0] == 'E') {
final_e = std::stod(word.substr(1));
max_e = std::max(max_e, final_e);
break;
}
}
}
return {final_e, max_e};
}
} // namespace
TEST_CASE("PA pattern resets the extruder after the final layer in absolute E mode", "[Calib][Regression]")
{
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({
{"use_relative_e_distances", "0"},
{"line_width", "0.45"},
{"initial_layer_line_width", "0.45"},
});
Model model;
model.add_object("cube", "", make_cube(20, 20, 20))->add_instance();
Calib_Params params;
params.mode = CalibMode::Calib_PA_Pattern;
params.start = 0.;
params.end = 0.08;
params.step = 0.002;
CalibPressureAdvancePattern pattern(params, config, /* is_bbl_machine */ false, *model.objects.front(), Vec3d(0, 0, 0));
const CustomGCode::Info info = pattern.generate_custom_gcodes(config, /* is_bbl_machine */ false, *model.objects.front(),
Vec3d(0, 0, 0));
std::string gcode;
for (const CustomGCode::Item &item : info.gcodes)
gcode += item.extra;
const EndState state = simulate_absolute_e(gcode);
REQUIRE(state.max_e > 1.);
REQUIRE_THAT(state.final_e, Catch::Matchers::WithinAbs(0., 1e-9));
}