From b8bf1a8fe1606323ff9a177d70d0faf285df1192 Mon Sep 17 00:00:00 2001 From: xiaoyeliu Date: Sat, 24 Jan 2026 10:43:54 +0800 Subject: [PATCH] Revert "Feature boundary test lxy (#128)" This reverts commit a1769a2148b2ea5b6468d99961ec5966c1ccf613. --- docs/gcode_boundary_checking_optimization.md | 1425 ----------------- docs/gcode_boundary_final_implementation.md | 521 ------ ...de_boundary_optimization_implementation.md | 772 --------- docs/release_risk_assessment.md | 619 ------- localization/i18n/Snapmaker_Orca.pot | 66 - .../i18n/zh_CN/Snapmaker_Orca_zh_CN.po | 57 - src/libslic3r/BoundaryValidator.cpp | 215 --- src/libslic3r/BoundaryValidator.hpp | 168 -- src/libslic3r/Brim.cpp | 63 +- src/libslic3r/Brim.hpp | 4 +- src/libslic3r/BuildVolume.cpp | 4 +- src/libslic3r/CMakeLists.txt | 2 - src/libslic3r/GCode.cpp | 7 - src/libslic3r/GCode/GCodeProcessor.cpp | 2 - src/libslic3r/GCode/GCodeProcessor.hpp | 66 +- src/libslic3r/GCodeWriter.cpp | 174 +- src/libslic3r/GCodeWriter.hpp | 14 - src/libslic3r/Print.cpp | 120 +- src/libslic3r/Print.hpp | 19 - src/libslic3r/PrintObject.cpp | 54 - src/libslic3r/Support/SupportMaterial.cpp | 88 - src/slic3r/GUI/GCodeViewer.cpp | 94 -- src/slic3r/GUI/GCodeViewer.hpp | 6 - src/slic3r/GUI/GLCanvas3D.cpp | 97 +- tools/README_gcode_checker.md | 312 ---- tools/analyze_gcode_bounds.py | 449 ------ tools/gcode_boundary_checker_gui.py | 674 -------- tools/run_gcode_checker.bat | 28 - 28 files changed, 23 insertions(+), 6097 deletions(-) delete mode 100644 docs/gcode_boundary_checking_optimization.md delete mode 100644 docs/gcode_boundary_final_implementation.md delete mode 100644 docs/gcode_boundary_optimization_implementation.md delete mode 100644 docs/release_risk_assessment.md delete mode 100644 src/libslic3r/BoundaryValidator.cpp delete mode 100644 src/libslic3r/BoundaryValidator.hpp delete mode 100644 tools/README_gcode_checker.md delete mode 100644 tools/analyze_gcode_bounds.py delete mode 100644 tools/gcode_boundary_checker_gui.py delete mode 100644 tools/run_gcode_checker.bat diff --git a/docs/gcode_boundary_checking_optimization.md b/docs/gcode_boundary_checking_optimization.md deleted file mode 100644 index ad9f4c5771..0000000000 --- a/docs/gcode_boundary_checking_optimization.md +++ /dev/null @@ -1,1425 +0,0 @@ -# OrcaSlicer G-code超限检测优化技术文档 - -> **项目编号**: ORCA-2026-001 -> **创建日期**: 2026-01-15 -> **作者**: Claude Code -> **状态**: ✅ 已完成 - -> **重要说明**:本文档是设计阶段的原始文档。实际实现与设计有一些差异: -> - 原设计在 `BuildVolume` 中添加 `all_moves_inside()` 方法 -> - **实际实现**:Travel 检查采用内联方式在 `GCodeViewer.cpp` 中实现,以便收集详细的违规信息(类型、方向、位置、距离) -> - 这是因为需要返回详细的 `BoundaryViolationInfo` 结构,而不是简单的布尔值 - ---- - -## 目录 - -1. [项目概述](#1-项目概述) -2. [当前系统架构分析](#2-当前系统架构分析) -3. [G-code生成和验证流程](#3-g-code生成和验证流程) -4. [问题分析](#4-问题分析) -5. [优化方案设计](#5-优化方案设计) -6. [风险评估](#6-风险评估) -7. [实施计划](#7-实施计划) -8. [参考资料](#8-参考资料) - ---- - -## 1. 项目概述 - -### 1.1 背景 - -OrcaSlicer 当前存在部分 G-code 路径超出打印边界但未能正确检测和警告的问题,这可能导致: -- 打印头撞击打印床边界导致硬件损坏 -- 打印失败但用户不知道原因 -- 用户体验差,对软件质量产生质疑 - -### 1.2 目标 - -**核心目标**:确保所有超出打印边界的 G-code 路径都能被检测并警告用户。 - -**具体目标**: -1. 调研并文档化当前 G-code 超限检测逻辑和框架 -2. 识别并修复所有超限切片不报错的场景 -3. 实现警告提示机制(允许继续但高亮警告) -4. 添加测试用例防止回归 - -### 1.3 交付物 - -- ✅ 完整的技术文档(本文档) -- ⏳ 修复所有8个已识别的关键漏洞 -- ⏳ 单元测试和集成测试用例 -- ⏳ 代码实现和 Code Review - ---- - -## 2. 当前系统架构分析 - -### 2.1 整体架构概述 - -OrcaSlicer 实现了**两套独立的超限检测系统**: - -#### 系统 A: 物体边界检测 (Object Bounds Checking) -- **功能**: 检测 3D 模型是否超出打印平台边界 -- **触发时机**: 模型放置、移动、导出前 -- **核心类**: `BuildVolume` -- **检测粒度**: 模型级别(基于包围盒和网格) - -#### 系统 B: G-code 路径冲突检测 (G-code Conflict Checking) -- **功能**: 检测切片后 G-code 路径是否发生对象间冲突 -- **触发时机**: 切片完成后 -- **核心类**: `ConflictChecker` -- **检测粒度**: 路径级别(基于线段相交) - -**关键发现**: 这两套系统各自独立,存在检测盲区。 - ---- - -### 2.2 核心类和文件结构 - -#### 2.2.1 冲突检测核心 - -**ConflictChecker** (`src/libslic3r/GCode/ConflictChecker.hpp/cpp`) - -```cpp -struct LineWithID { - Line _line; // 线段几何 - const void * _id; // 所属对象指针 - ExtrusionRole _role; // 挤出角色(支撑/填充/外壁等) -}; - -struct ConflictChecker { - static ConflictResultOpt find_inter_of_lines_in_diff_objs( - PrintObjectPtrs objs, - std::optional wtdptr - ); - static ConflictComputeOpt find_inter_of_lines(const LineWithIDs &lines); - static ConflictComputeOpt line_intersect(const LineWithID &l1, const LineWithID &l2); -}; -``` - -**核心算法**: -1. **栅格化加速** (Rasterization):使用 1mm × 1mm 网格将线段映射到空间 -2. **并行检测**:使用 TBB 并行处理各层 -3. **早期退出**:找到第一个冲突即停止 - -**性能优化**: -- 复杂度从 O(n²) 降低到接近 O(n) -- 使用 3D DDA 算法进行高效栅格化 - -**关键代码位置**: -- 栅格化: `ConflictChecker.cpp:25-86` -- 冲突检测主函数: `ConflictChecker.cpp:220-284` -- 线段相交判定: `ConflictChecker.cpp:286-308` - -#### 2.2.2 打印体积边界检测核心 - -**BuildVolume** (`src/libslic3r/BuildVolume.hpp/cpp`) - -```cpp -enum class BuildVolume_Type { - Invalid, - Rectangle, // 矩形打印床(最常见) - Circle, // 圆形打印床(Delta 打印机) - Convex, // 凸多边形打印床 - Custom // 自定义形状打印床 -}; - -enum class ObjectState { - Inside, // 完全在打印体积内,可打印 - Colliding, // 与边界碰撞,不可打印 - Outside, // 完全在打印体积外,被忽略 - Below, // 完全在打印床下方 -}; - -class BuildVolume { - ObjectState object_state(const indexed_triangle_set &its, - const Transform3f &trafo, - bool may_be_below_bed, - bool ignore_bottom = true) const; - - bool all_paths_inside(const GCodeProcessorResult& paths, - const BoundingBoxf3& paths_bbox, - bool ignore_bottom = true) const; -}; -``` - -**关键方法**: -- `object_state()`: 检测物体是否超出打印体积 (`BuildVolume.cpp:280-313`) -- `all_paths_inside()`: 检测 G-code 路径是否在边界内 (`BuildVolume.cpp:328-367`) - -**容差配置**: -- `SceneEpsilon = EPSILON`:用于物体检测 -- `BedEpsilon = 3 * EPSILON`:用于 G-code 检测(更宽松) - -#### 2.2.3 主控制流程 - -**Print 类** (`src/libslic3r/Print.hpp/cpp`) - -```cpp -class Print { - // 验证方法 - std::string validate() const; // Print.cpp:1061 - bool sequential_print_clearance_valid(); // Print.cpp:560-850 - - // 冲突检测结果 - ConflictResultOpt m_conflict_result; // Print.hpp:1063 - - // 导出 G-code - std::string export_gcode(const std::string& path, - GCodeProcessorResult* result); // Print.cpp:2224 -}; -``` - -**Print 步骤枚举**: -```cpp -enum PrintStep { - psWipeTower, - psToolOrdering, - psSkirtBrim, - psSlicingFinished, - psGCodeExport, - psConflictCheck // ← 冲突检测步骤 -}; -``` - -**冲突检测触发** (`Print.cpp:2196-2215`): -```cpp -if (!m_no_check && !has_adaptive_layer_height) { - auto conflictRes = ConflictChecker::find_inter_of_lines_in_diff_objs( - m_objects, wipe_tower_opt - ); - m_conflict_result = conflictRes; - if (conflictRes.has_value()) { - BOOST_LOG_TRIVIAL(error) << "gcode path conflicts found..."; - } -} -``` - -**注意**:自适应层高时不执行冲突检测(因为 FakeWipeTower 使用固定层高)。 - -#### 2.2.4 GUI 显示和通知 - -**GLCanvas3D** (`src/slic3r/GUI/GLCanvas3D.cpp`) - -```cpp -enum class EWarning { - ObjectOutside, // 物体超出边界 - ToolpathOutside, // 路径超出边界 - ToolHeightOutside, // 路径超出最大高度 - GCodeConflict, // G-code 冲突 - // ... -}; -``` - -**警告显示** (`GLCanvas3D.cpp:9666-9681`): -```cpp -case EWarning::GCodeConflict: - text = (boost::format( - "Conflicts of G-code paths have been found at layer %d, z = %.2lf mm. " - "Please separate the conflicted objects farther (%s <-> %s)." - ) % layer % height % objName1 % objName2).str(); - - error = ErrorType::SLICING_SERIOUS_WARNING; -``` - -**警告级别**: -- `SLICING_ERROR` - 错误(红色) -- `SLICING_SERIOUS_WARNING` - 严重警告(橙色) -- `PLATER_WARNING` - 普通警告(黄色) - ---- - -### 2.3 检测触发时机总览 - -``` -时间线: 用户操作 → 模型加载 → 切片 → G-code 生成 → 导出 - -┌─────────────────┬────────────────────────────────────────────────────┐ -│ 阶段 │ 检测内容 │ -├─────────────────┼────────────────────────────────────────────────────┤ -│ 模型加载 │ Model::update_print_volume_state() │ -│ │ - 检测模型是否在床内 │ -│ │ - 使用 BuildVolume::object_state() │ -├─────────────────┼────────────────────────────────────────────────────┤ -│ 模型移动/修改 │ Plater::update_print_volume_state() │ -│ │ - 实时检测边界状态 │ -│ │ - GLCanvas3D::requires_check_outside_state() │ -├─────────────────┼────────────────────────────────────────────────────┤ -│ 切片前验证 │ Print::validate() │ -│ │ - 检查打印高度 │ -│ │ - 检查挤出头间隙 │ -│ │ - 检查床排除区域 │ -│ │ - 检查对象碰撞 │ -├─────────────────┼────────────────────────────────────────────────────┤ -│ 切片完成后 │ ConflictChecker::find_inter_of_lines_in_diff_objs()│ -│ │ - 检测对象间路径冲突 │ -│ │ - 存储到 m_conflict_result │ -├─────────────────┼────────────────────────────────────────────────────┤ -│ G-code 加载 │ GCodeViewer::load() │ -│ │ - BuildVolume::all_paths_inside() │ -│ │ - 检测挤出路径是否在床内 │ -├─────────────────┼────────────────────────────────────────────────────┤ -│ 导出前 │ Plater::get_export_file_path() │ -│ │ - 最终边界状态检查 │ -└─────────────────┴────────────────────────────────────────────────────┘ -``` - ---- - -## 3. G-code生成和验证流程 - -### 3.1 G-code 生成主流程 - -``` -Print::export_gcode() - └→ GCode::do_export() - └→ GCode::_do_export() - ├→ [初始化阶段] - │ ├─ 初始化 GCodeProcessor - │ ├─ 创建 SpiralVase (如启用) - │ ├─ 创建 PressureEqualizer (如启用) - │ └─ 创建 SmallAreaInfillCompensator - │ - ├→ [头部生成] - │ ├─ 写入头部块 - │ ├─ 生成缩略图 - │ └─ 写入配置块 - │ - └→ [层处理管道] GCode::process_layers() - └→ 并行处理各层 (TBB) - ├─ Stage 1: 生成原始 G-code (process_layer) - ├─ Stage 2: 应用螺旋花瓶 (可选) - ├─ Stage 3: 应用压力均衡器 (可选) - ├─ Stage 4: 应用冷却缓冲 (CoolingBuffer) - ├─ Stage 5: 应用自适应 PA 处理器 - └─ Stage 6: 写入输出流 -``` - -**关键文件**: -- 入口: `Print.cpp:2224` - `Print::export_gcode()` -- 主逻辑: `GCode.cpp:1845` - `GCode::_do_export()` -- 层处理: `GCode.cpp:2770` - `GCode::process_layers()` -- 单层处理: `GCode.cpp:3619` - `GCode::process_layer()` - -### 3.2 验证阶段详解 - -#### 验证点 1: 切片前验证 (Pre-Slicing Validation) - -**函数**: `Print::validate()` (`Print.cpp:1061`) - -**检查项**: - -| 检查项 | 代码位置 | 说明 | -|--------|---------|------| -| 挤出头间隙验证 | Print.cpp:560-850 | `sequential_print_clearance_valid()` | -| 打印高度检查 | Print.cpp:1135-1164 | 验证不超过 `printable_height` | -| 床排除区域检查 | Print.cpp:634-647 | `get_bed_excluded_area()` | -| 对象碰撞检测 | Print.cpp:650-680 | 检查凸包碰撞 | -| 多材料兼容性 | Print.cpp:1072-1079 | `check_multi_filament_valid()` | -| 螺旋花瓶验证 | Print.cpp:1101-1118 | 单对象/材料约束 | -| 擦料塔验证 | Print.cpp:1183-1220 | 喷嘴/耗材直径一致性 | - -#### 验证点 2: G-code 生成时验证 (During Generation) - -**函数**: `GCode::travel_to()` 及相关 - -**检查项**: -- 移动路径验证:`needs_retraction()` 检查是否需要回抽 -- 避免穿越外壁:`AvoidCrossingPerimeters` 类 -- 穿越时回抽:`RetractWhenCrossingPerimeters` 类 - -**关键文件**: -- `src/libslic3r/GCode/AvoidCrossingPerimeters.hpp` -- `src/libslic3r/GCode/RetractWhenCrossingPerimeters.hpp` - -#### 验证点 3: 切片后验证 (Post-Processing) - -**ConflictChecker** (`ConflictChecker.cpp:145`) - -**算法流程**: -``` -1. 收集所有对象的挤出路径 - ├─ getAllLayersExtrusionPathsFromObject() - ├─ 提取 perimeters 和 support 路径 - └─ 添加 FakeWipeTower 路径(如有) - -2. 按层组织线段 - ├─ 使用 LinesBucketQueue 按 Z 高度排序 - ├─ 逐层提取当前高度的所有线段 - └─ 存储到 layersLines 向量 - -3. 并行检测相交 (TBB) - ├─ 对每一层调用 find_inter_of_lines() - ├─ 使用栅格化加速相交检测 - └─ 找到第一个冲突即返回 - -4. 构造冲突结果 - ├─ 记录冲突对象名称 - ├─ 记录冲突 Z 高度 - └─ 返回 ConflictResult -``` - -**线段相交判定** (`ConflictChecker.cpp:286-308`): -```cpp -// 关键参数 -constexpr double SUPPORT_THRESHOLD = 100.0; // 支撑材料阈值(实际禁用) -constexpr double OTHER_THRESHOLD = 0.01; // 常规材料阈值 - -// 判定逻辑 -1. 如果两线段属于同一对象 → 返回无冲突 -2. 计算几何相交点 -3. 计算相交点到线段端点的最小距离 -4. 如果距离 > 阈值 → 认为发生冲突 -``` - -#### 验证点 4: GCodeProcessor 验证 - -**函数**: `GCodeProcessor::process_gcode_line()` 等 - -**检查项**: -- 移动命令验证 (G0/G1) -- 路径超限检测 (`toolpath_outside` 标志) -- 床温度验证 (`update_slice_warnings()`) -- 喷嘴 HRC 验证 -- 时间估算验证(模拟固件加速度限制) - ---- - -### 3.3 核心类协作关系 - -```mermaid -graph TB - Print[Print 类] --> |切片前验证| Validate[validate 方法] - Print --> |导出 G-code| GCode[GCode 类] - GCode --> |生成路径| GCodeWriter[GCodeWriter 类] - GCodeWriter --> |写入命令| Output[输出流] - - Print --> |切片后检测| ConflictChecker[ConflictChecker] - ConflictChecker --> |检测结果| ConflictResult[ConflictResult] - - GCode --> |处理 G-code| GCodeProcessor[GCodeProcessor] - GCodeProcessor --> |路径检测| BuildVolume[BuildVolume] - BuildVolume --> |边界检查| all_paths_inside[all_paths_inside 方法] - - ConflictResult --> |显示警告| GLCanvas3D[GLCanvas3D] - all_paths_inside --> |toolpath_outside| GCodeViewer[GCodeViewer] - GCodeViewer --> |显示警告| GLCanvas3D -``` - ---- - -## 4. 问题分析 - -### 4.1 已识别的 8 个关键漏洞 - -#### 漏洞 1: 螺旋抬升超限 (Spiral Lift) - -**位置**: `src/libslic3r/GCodeWriter.cpp:547-552` - -**当前代码**: -```cpp -if (m_to_lift_type == LiftType::SpiralLift && this->is_current_position_clear()) { - //BBS: todo: check the arc move all in bed area, if not, then use lazy lift - double radius = delta(2) / (2 * PI * atan(this->extruder()->travel_slope())); - Vec2d ij_offset = radius * delta_no_z.normalized(); - ij_offset = { -ij_offset(1), ij_offset(0) }; - slop_move = this->_spiral_travel_to_z(target(2), ij_offset, "spiral lift Z"); -} -``` - -**问题描述**: -- 代码中已有 TODO 注释说明需要检查弧线路径 -- 螺旋抬升生成 G2/G3 弧线命令,可能超出打印区域 -- 弧线半径 = `delta_z / (2 * PI * tan(slope))`,当Z抬升较大时半径可能很大 - -**影响范围**: -- 使用螺旋抬升功能的所有打印 -- 特别是物体靠近床边缘时 - -**风险等级**: ⭐⭐⭐⭐ (高风险) - ---- - -#### 漏洞 2: 懒惰抬升超限 (Lazy Lift Slope) - -**位置**: `src/libslic3r/GCodeWriter.cpp:555-568` - -**当前代码**: -```cpp -else if (m_to_lift_type == LiftType::LazyLift && ...) { - Vec2d temp = delta_no_z.normalized() * delta(2) / tan(this->extruder()->travel_slope()); - Vec3d slope_top_point = Vec3d(temp(0), temp(1), delta(2)) + source; - // 直接生成 G-code,没有边界检查 - GCodeG1Formatter w0; - w0.emit_xyz(slope_top_point); - w0.emit_f(travel_speed * 60.0); - slop_move = w0.string(); -} -``` - -**问题描述**: -- 计算斜坡顶点位置但不验证是否在边界内 -- `slope_top_point` 可能超出打印床 - -**影响范围**: -- 使用懒惰抬升功能的打印 -- 长距离 travel 移动时风险更大 - -**风险等级**: ⭐⭐⭐ (中高风险) - ---- - -#### 漏洞 3: 擦料塔位置超限 (Wipe Tower) - -**位置**: `src/libslic3r/Print.cpp:943-977` - -**当前代码**: -```cpp -float x = config.wipe_tower_x.get_at(plate_index) + plate_origin(0); -float y = config.wipe_tower_y.get_at(plate_index) + plate_origin(1); -float width = config.prime_tower_width.value; - -// 只检查与对象和排除区域的碰撞 -// 没有检查擦料塔本身是否在床边界内! -Polygon wipe_tower_convex_hull = /* ... */; -if (intersects(wipe_tower_convex_hull, object_convex_hull)) { - // 报错 -} -``` - -**问题描述**: -- 只检查擦料塔与其他物体的碰撞 -- 不检查擦料塔本身是否超出床边界 -- 擦料塔包括 brim 和稳定锥,实际占用面积大于配置的宽度 - -**影响范围**: -- 所有多材料打印(使用擦料塔) -- 用户手动设置擦料塔位置时 - -**风险等级**: ⭐⭐⭐⭐⭐ (极高风险 - 应为阻断性错误) - ---- - -#### 漏洞 4: 裙边超限 (Skirt) - -**位置**: `src/libslic3r/Print.cpp:2338-2357` - -**当前代码**: -```cpp -distance += float(scale_(spacing)); -Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, float(scale_(0.1))); - -// 没有边界检查! -// 直接创建挤出路径 -``` - -**问题描述**: -- 通过偏移凸包生成裙边 -- 不验证裙边是否超出床边界 -- 大物体 + 大裙边距离 = 必超限 - -**影响范围**: -- 使用裙边功能的打印(很常见) -- 大物体接近床边缘时 - -**风险等级**: ⭐⭐⭐ (中高风险) - ---- - -#### 漏洞 5: 边缘超限 (Brim) - -**位置**: `src/libslic3r/Brim.cpp` - -**问题描述**: -- Brim 生成后不验证边界 -- 类似裙边问题 -- Brim 通常比 Skirt 更宽 - -**影响范围**: -- 使用 Brim 功能的打印 -- 提高附着力时常用 - -**风险等级**: ⭐⭐⭐ (中高风险) - ---- - -#### 漏洞 6: 支撑材料超限 (Support Material) - -**位置**: `src/libslic3r/SupportMaterial.cpp`, `src/libslic3r/Support/TreeSupport.cpp` - -**问题描述**: -- 支撑材料自动生成算法 -- 没有明确的边界验证步骤 -- 树形支撑可能延伸到模型外很远 - -**影响范围**: -- 使用支撑材料的打印 -- 特别是树形支撑 - -**风险等级**: ⭐⭐ (中风险) - ---- - -#### 漏洞 7: Travel Moves 不验证 ⚠️ **严重** - -**位置**: `src/libslic3r/BuildVolume.cpp:328-367` - -**当前代码**: -```cpp -bool BuildVolume::all_paths_inside(...) const { - auto move_valid = [](const GCodeProcessorResult::MoveVertex &move) { - // 只检查挤出移动! - return move.type == EMoveType::Extrude && - move.extrusion_role != erCustom && - move.width != 0.f && - move.height != 0.f; - }; - - // 所有 Travel 移动都被跳过验证 - return std::all_of(paths.moves.begin(), paths.moves.end(), - [move_valid, ...](const GCodeProcessorResult::MoveVertex &move) { - return !move_valid(move) || /* 边界检查 */; - }); -} -``` - -**问题描述**: -- **只验证挤出移动 (Extrude),不验证 Travel 移动** -- Travel 移动可能超出边界导致撞机 -- 这是一个设计缺陷,不是实现bug - -**影响范围**: -- 所有打印的所有 Travel 移动 -- 影响面最广 - -**风险等级**: ⭐⭐⭐⭐⭐ (极高风险 - 系统性缺陷) - ---- - -#### 漏洞 8: 弧线路径超限 (Arc G2/G3) - -**位置**: `src/libslic3r/GCodeWriter.cpp:673-691, 732-752` - -**当前代码**: -```cpp -std::string GCodeWriter::_spiral_travel_to_z(double z, const Vec2d& ij_offset, ...) { - // 生成 G2/G3 弧线命令 - // 只检查端点,不检查弧线路径上的中间点 - GCodeG2G3Formatter w; - w.emit_ij(ij_offset); - // ... -} -``` - -**问题描述**: -- 弧线命令只验证端点 -- 弧线路径上的中间点可能超出边界 -- 适用于螺旋抬升和弧形挤出 - -**影响范围**: -- 所有使用弧线命令的功能 -- ArcWelder 功能 - -**风险等级**: ⭐⭐⭐ (中高风险) - ---- - -### 4.2 根本原因分析 - -#### 原因 1: 缺乏统一的边界验证接口 - -**问题**: -- 边界检测逻辑分散在多个类中 -- 每个功能模块独立实现(或不实现)边界检查 -- 没有强制的边界验证规范 - -**影响**: -- 新功能容易忘记添加边界检查 -- 难以维护和审查 - -#### 原因 2: Travel Moves 被排除在验证之外 - -**问题**: -- `all_paths_inside()` 设计时只考虑挤出路径 -- 假设 Travel 移动不重要(错误假设) - -**影响**: -- 系统性漏洞,影响面最广 - -#### 原因 3: 特殊路径生成缺少验证步骤 - -**问题**: -- 螺旋抬升、懒惰抬升等特殊功能 -- 直接生成 G-code,绕过了验证流程 - -**影响**: -- 这些功能成为"盲区" - -#### 原因 4: 弧线路径只检查端点 - -**问题**: -- 弧线是曲线,端点在边界内不代表整条弧线在边界内 -- 缺少弧线采样验证 - -**影响**: -- 弧线相关功能存在风险 - -#### 原因 5: 几何计算与验证分离 - -**问题**: -- 先计算几何(偏移、弧线等) -- 后生成路径 -- 中间没有验证步骤 - -**影响**: -- 很多"计算完就直接用"的场景 - ---- - -### 4.3 影响分析矩阵 - -| 漏洞 | 触发频率 | 严重程度 | 用户感知 | 风险等级 | 修复优先级 | -|------|----------|----------|----------|----------|------------| -| Travel Moves 不验证 | 极高 | 极高 | 高 | ⭐⭐⭐⭐⭐ | P0 | -| 擦料塔位置超限 | 高 | 极高 | 高 | ⭐⭐⭐⭐⭐ | P0 | -| 螺旋抬升超限 | 中 | 高 | 中 | ⭐⭐⭐⭐ | P1 | -| Brim 超限 | 高 | 中 | 中 | ⭐⭐⭐ | P1 | -| Skirt 超限 | 高 | 中 | 中 | ⭐⭐⭐ | P1 | -| 弧线路径超限 | 低 | 高 | 低 | ⭐⭐⭐ | P2 | -| 懒惰抬升超限 | 低 | 中 | 低 | ⭐⭐⭐ | P2 | -| 支撑材料超限 | 低 | 中 | 低 | ⭐⭐ | P2 | - -**优先级定义**: -- **P0**: 立即修复(极高风险) -- **P1**: 高优先级(高风险) -- **P2**: 中等优先级(中风险) - ---- - -## 5. 优化方案设计 - -### 5.1 核心设计原则 - -1. **统一边界检测接口**: 创建 `BoundaryValidator` 抽象层 -2. **分层验证**: 在多个阶段验证(路径生成时、G-code生成后、最终输出前) -3. **类型化警告系统**: 扩展 `ConflictResult` 支持多种超限类型 -4. **非侵入式修复**: 尽量通过扩展而非修改核心逻辑 -5. **性能优先**: 使用采样而非详尽检查,避免影响切片速度 - ---- - -### 5.2 架构设计 - -#### 5.2.1 新增 BoundaryValidator 抽象类 - -**文件**: `src/libslic3r/BoundaryValidator.hpp` (新建) - -```cpp -namespace Slic3r { - -class BoundaryValidator { -public: - enum class ViolationType { - SpiralLiftOutOfBounds, - LazyLiftOutOfBounds, - WipeTowerOutOfBounds, - SkirtOutOfBounds, - BrimOutOfBounds, - SupportOutOfBounds, - TravelMoveOutOfBounds, - ArcPathOutOfBounds - }; - - struct BoundaryViolation { - ViolationType type; - std::string description; - Vec3d position; // 超限位置 - double layer_z; // Z高度 - std::string object_name; // 相关对象名称 - }; - - using BoundaryViolations = std::vector; - - virtual ~BoundaryValidator() = default; - - // 核心验证方法 - virtual bool validate_point(const Vec3d& point) const = 0; - virtual bool validate_line(const Vec3d& from, const Vec3d& to) const = 0; - virtual bool validate_arc(const Vec3d& center, double radius, - double start_angle, double end_angle, - double z_height) const = 0; - virtual bool validate_polygon(const Polygon& poly, double z_height = 0.0) const = 0; -}; - -// 基于 BuildVolume 的具体实现 -class BuildVolumeBoundaryValidator : public BoundaryValidator { - const BuildVolume& m_build_volume; - double m_epsilon; - -public: - BuildVolumeBoundaryValidator(const BuildVolume& bv, - double epsilon = BuildVolume::BedEpsilon) - : m_build_volume(bv), m_epsilon(epsilon) {} - - bool validate_point(const Vec3d& point) const override; - bool validate_line(const Vec3d& from, const Vec3d& to) const override; - bool validate_arc(const Vec3d& center, double radius, - double start_angle, double end_angle, - double z_height) const override; - bool validate_polygon(const Polygon& poly, double z_height = 0.0) const override; - -private: - // 弧线采样:在弧线上采样N个点进行验证 - std::vector sample_arc_points(const Vec3d& center, double radius, - double start_angle, double end_angle, - double z_height, - int num_samples = 16) const; -}; - -} // namespace Slic3r -``` - -**设计要点**: -- 抽象接口便于未来扩展(例如自定义验证逻辑) -- 提供点、线、弧、多边形四种基本验证 -- 弧线验证使用采样方法(默认16个采样点) -- 可配置 epsilon 容差 - ---- - -#### 5.2.2 扩展 ConflictResult - -**文件**: `src/libslic3r/GCode/GCodeProcessor.hpp` (修改) - -```cpp -struct ConflictResult { - // ===== 现有字段 ===== - std::string _objName1; - std::string _objName2; - double _height; - const void * _obj1; - const void * _obj2; - int layer; - - // ===== 新增字段 ===== - enum class ConflictType { - ObjectCollision, // 对象间冲突(原有) - BoundaryViolation // 边界超限(新增) - }; - - ConflictType conflict_type = ConflictType::ObjectCollision; - - // 仅当 conflict_type == BoundaryViolation 时有效 - BoundaryValidator::ViolationType violation_type; - Vec3d violation_position; - - // 构造函数 - ConflictResult() = default; - - // 对象冲突构造函数(保持兼容) - ConflictResult(const void* o1, const void* o2, double h) - : _obj1(o1), _obj2(o2), _height(h), - conflict_type(ConflictType::ObjectCollision) {} - - // 边界超限构造函数(新增) - static ConflictResult create_boundary_violation( - BoundaryValidator::ViolationType type, - const Vec3d& pos, - double height, - const std::string& obj_name = "" - ) { - ConflictResult result; - result.conflict_type = ConflictType::BoundaryViolation; - result.violation_type = type; - result.violation_position = pos; - result._height = height; - result._objName1 = obj_name; - result.layer = -1; // 稍后计算 - return result; - } -}; -``` - ---- - -#### 5.2.3 在 Print 类中添加边界超限收集 - -**文件**: `src/libslic3r/Print.hpp` (修改) - -```cpp -class Print { - // ===== 现有成员 ===== - ConflictResultOpt m_conflict_result; - - // ===== 新增成员 ===== - std::vector m_boundary_violations; - -public: - // 新增方法 - void add_boundary_violation(const ConflictResult& violation) { - m_boundary_violations.push_back(violation); - } - - const std::vector& get_boundary_violations() const { - return m_boundary_violations; - } - - void clear_boundary_violations() { - m_boundary_violations.clear(); - } -}; -``` - ---- - -### 5.3 具体修复方案 - -#### 修复方案 1: Travel Moves 验证 (P0) - -> **实际实现说明**:设计方案中提出了在 `BuildVolume` 中添加 `all_moves_inside()` 方法,但最终实现采用了不同的方式。 - -**实际实现位置**: `src/slic3r/GUI/GCodeViewer.cpp:2427-2477` - -**实现方式**: 内联检查(而非调用 BuildVolume 方法) - -**为什么采用内联实现**: -- 需要收集详细的违规信息:类型、方向、位置、距离、Z高度 -- 简单的布尔返回值无法提供诊断数据 -- 内联实现可以直接填充 `BoundaryViolationInfo` 结构 - -**设计方案(未采用)**: 以下是原始设计方案,供参考 - -**文件**: `src/libslic3r/BuildVolume.hpp/cpp` (设计方案,未实施) - -**新增方法**: `all_moves_inside()` (检查所有移动,包括 Travel) - -```cpp -// BuildVolume.hpp -bool all_moves_inside(const GCodeProcessorResult& paths, - const BoundingBoxf3& paths_bbox, - bool ignore_bottom = true) const; - -// BuildVolume.cpp -bool BuildVolume::all_moves_inside(const GCodeProcessorResult& paths, - const BoundingBoxf3& paths_bbox, - bool ignore_bottom) const -{ - auto move_significant = [](const GCodeProcessorResult::MoveVertex &move) { - // 验证所有移动,排除回抽/反回抽 - return move.type != EMoveType::Retract && - move.type != EMoveType::Unretract; - }; - - static constexpr const double epsilon = BedEpsilon; - - switch (m_type) { - case BuildVolume_Type::Rectangle: - { - BoundingBox3Base build_volume = this->bounding_volume().inflated(epsilon); - if (m_max_print_height == 0.0) - build_volume.max.z() = std::numeric_limits::max(); - if (ignore_bottom) - build_volume.min.z() = -std::numeric_limits::max(); - - return std::all_of(paths.moves.begin(), paths.moves.end(), - [move_significant, &build_volume](const GCodeProcessorResult::MoveVertex &move) { - return !move_significant(move) || build_volume.contains(move.position); - }); - } - case BuildVolume_Type::Circle: - { - const Vec2f c = unscaled(m_circle.center); - const float r = unscaled(m_circle.radius) + epsilon; - const float r2 = sqr(r); - return m_max_print_height == 0.0 ? - std::all_of(paths.moves.begin(), paths.moves.end(), - [move_significant, c, r2](const GCodeProcessorResult::MoveVertex &move) { - return !move_significant(move) || - (to_2d(move.position) - c).squaredNorm() <= r2; - }) : - std::all_of(paths.moves.begin(), paths.moves.end(), - [move_significant, c, r2, z = m_max_print_height + epsilon] - (const GCodeProcessorResult::MoveVertex& move) { - return !move_significant(move) || - ((to_2d(move.position) - c).squaredNorm() <= r2 && - move.position.z() <= z); - }); - } - case BuildVolume_Type::Convex: - case BuildVolume_Type::Custom: - return m_max_print_height == 0.0 ? - std::all_of(paths.moves.begin(), paths.moves.end(), - [move_significant, this](const GCodeProcessorResult::MoveVertex &move) { - return !move_significant(move) || - Geometry::inside_convex_polygon( - m_top_bottom_convex_hull_decomposition_bed, - to_2d(move.position).cast()); - }) : - std::all_of(paths.moves.begin(), paths.moves.end(), - [move_significant, this, z = m_max_print_height + epsilon] - (const GCodeProcessorResult::MoveVertex &move) { - return !move_significant(move) || - (Geometry::inside_convex_polygon( - m_top_bottom_convex_hull_decomposition_bed, - to_2d(move.position).cast()) && - move.position.z() <= z); - }); - default: - return true; - } -} -``` - -**调用位置**: `GCodeViewer::load()` 中添加调用 - -```cpp -// GCodeViewer.cpp -if (!build_volume.all_moves_inside(gcode_result, paths_bbox)) { - m_toolpath_outside = true; - // 记录具体的超限移动 - for (const auto& move : gcode_result.moves) { - if ((move.type == EMoveType::Travel || move.type == EMoveType::Extrude) && - !build_volume.contains(move.position)) { - // 记录超限位置 - } - } -} -``` - ---- - -#### 修复方案 2: 擦料塔位置验证 (P0) - -**文件**: `src/libslic3r/Print.cpp` - -**修改位置**: `Print::validate()` 方法 - -```cpp -// Print.cpp - validate() 方法中添加 -if (config.prime_tower_width > 0) { - const size_t plate_index = 0; // 获取当前板索引 - float x = config.wipe_tower_x.get_at(plate_index) + plate_origin(0); - float y = config.wipe_tower_y.get_at(plate_index) + plate_origin(1); - float width = config.prime_tower_width.value; - float brim_width = config.prime_tower_brim_width.value; - - // 构造擦料塔多边形(包括 brim) - float total_width = width + 2 * brim_width; - Polygon wipe_tower_polygon; - wipe_tower_polygon.points.push_back(Point(scale_(x), scale_(y))); - wipe_tower_polygon.points.push_back(Point(scale_(x + total_width), scale_(y))); - wipe_tower_polygon.points.push_back(Point(scale_(x + total_width), scale_(y + total_width))); - wipe_tower_polygon.points.push_back(Point(scale_(x), scale_(y + total_width))); - - // 验证擦料塔是否在床边界内 - BuildVolumeBoundaryValidator validator(this->build_volume()); - if (!validator.validate_polygon(wipe_tower_polygon, 0.0)) { - throw Slic3r::SlicingError( - (boost::format( - "The wipe tower at position (%.2f, %.2f) with width %.2f " - "(including %.2f mm brim) exceeds the bed boundaries. " - "Please adjust the wipe tower position in the configuration." - ) % x % y % total_width % brim_width).str() - ); - } - - // 还需要检查擦料塔是否与床排除区域冲突 - // ... (现有逻辑保持) -} -``` - -**注意**: 这是**阻断性错误**,不允许切片继续。 - ---- - -#### 修复方案 3: 螺旋抬升验证 (P1) - -**文件**: `src/libslic3r/GCodeWriter.cpp` - -**前提**: `GCodeWriter` 需要访问 `BoundaryValidator` - -```cpp -// GCodeWriter.hpp 添加成员 -class GCodeWriter { - const BoundaryValidator* m_boundary_validator = nullptr; - std::vector* m_violations = nullptr; - -public: - void set_boundary_validator(const BoundaryValidator* validator, - std::vector* violations) { - m_boundary_validator = validator; - m_violations = violations; - } -}; -``` - -**修改 travel_to_z() 方法**: - -```cpp -// GCodeWriter.cpp:545-575 -std::string GCodeWriter::travel_to_z(double z, const std::string& comment) { - // ... 现有代码 ... - - if (delta(2) > 0 && delta_no_z.norm() != 0.0f) { - if (m_to_lift_type == LiftType::SpiralLift && this->is_current_position_clear()) { - double radius = delta(2) / (2 * PI * atan(this->extruder()->travel_slope())); - Vec2d ij_offset = radius * delta_no_z.normalized(); - ij_offset = { -ij_offset(1), ij_offset(0) }; - - // ===== 新增:边界验证 ===== - if (m_boundary_validator) { - Vec3d arc_center = source + Vec3d(ij_offset.x(), ij_offset.y(), 0); - double start_angle = atan2(-ij_offset.y(), -ij_offset.x()); - double end_angle = start_angle + 2 * PI; - - if (!m_boundary_validator->validate_arc(arc_center, radius, - start_angle, end_angle, source.z())) { - // 记录超限警告 - if (m_violations) { - m_violations->push_back({ - BoundaryValidator::ViolationType::SpiralLiftOutOfBounds, - "Spiral lift arc exceeds bed boundaries", - arc_center, - source.z(), - "" - }); - } - // 降级为 LazyLift - BOOST_LOG_TRIVIAL(warning) << "Spiral lift exceeds boundaries, falling back to lazy lift"; - m_to_lift_type = LiftType::LazyLift; - // 重新执行(会进入 LazyLift 分支) - return this->travel_to_z(z, comment); - } - } - - slop_move = this->_spiral_travel_to_z(target(2), ij_offset, "spiral lift Z"); - } - else if (m_to_lift_type == LiftType::LazyLift && ...) { - Vec2d temp = delta_no_z.normalized() * delta(2) / tan(this->extruder()->travel_slope()); - Vec3d slope_top_point = Vec3d(temp(0), temp(1), delta(2)) + source; - - // ===== 新增:边界验证 ===== - if (m_boundary_validator && - !m_boundary_validator->validate_point(slope_top_point)) { - // 记录超限警告 - if (m_violations) { - m_violations->push_back({ - BoundaryValidator::ViolationType::LazyLiftOutOfBounds, - "Lazy lift slope exceeds bed boundaries", - slope_top_point, - source.z(), - "" - }); - } - // 降级为 NormalLift - BOOST_LOG_TRIVIAL(warning) << "Lazy lift exceeds boundaries, falling back to normal lift"; - slop_move = _travel_to_z(target.z(), "normal lift Z (fallback)"); - } else { - GCodeG1Formatter w0; - w0.emit_xyz(slope_top_point); - w0.emit_f(travel_speed * 60.0); - w0.emit_comment(GCodeWriter::full_gcode_comment, comment); - slop_move = w0.string(); - } - } - // ... 其他分支 ... - } - - // ... 现有代码 ... -} -``` - ---- - -#### 其他修复方案摘要 - -**Skirt/Brim/Support**: 类似策略,在生成后添加验证,记录警告但不阻断。 - -**Arc 路径**: 在 `_spiral_travel_to_z()` 和 `extrude_arc_to_xy()` 中使用 `validate_arc()`。 - ---- - -## 6. 风险评估 - -### 6.1 技术风险 - -| 风险 | 描述 | 影响 | 缓解措施 | 优先级 | -|------|------|------|----------|--------| -| 性能影响 | 增加边界检测可能拖慢切片 | 用户体验下降 | 使用采样而非详尽检查、缓存结果 | 高 | -| 误报 | 过于严格导致正常打印也报警 | 用户信任度下降 | 合理设置 epsilon、充分测试 | 高 | -| 兼容性 | 修改可能影响现有功能 | 功能回归 | 充分的回归测试 | 中 | -| 复杂性 | 新增抽象层增加代码复杂度 | 维护成本上升 | 清晰的文档和注释 | 低 | - -### 6.2 实施风险 - -| 风险 | 描述 | 影响 | 缓解措施 | 优先级 | -|------|------|------|----------|--------| -| 擦料塔阻断性错误 | 改为阻断可能影响现有用户 | 用户不满 | 提供清晰的错误提示和修复建议 | 中 | -| 测试覆盖不足 | 边界条件难以穷举 | 隐藏 bug | 收集用户反馈、持续改进 | 高 | -| 回归 bug | 修改核心逻辑导致新bug | 功能损坏 | 严格的 Code Review | 高 | - ---- - -## 7. 实施计划 - -### 7.1 里程碑规划 - -#### 里程碑 1: 基础设施 (1 周) -- 创建 `BoundaryValidator` 类 -- 扩展 `ConflictResult` -- 添加基础单元测试 - -#### 里程碑 2: P0 修复 (1 周) -- 修复 Travel Moves 验证 -- 修复擦料塔位置验证 -- 集成测试 - -#### 里程碑 3: P1 修复 (1 周) -- 修复螺旋抬升 -- 修复 Skirt/Brim -- 集成测试 - -#### 里程碑 4: P2 修复和 GUI (1 周) -- 修复剩余问题 -- GUI 警告系统 -- 可视化 - -#### 里程碑 5: 文档和收尾 (0.5 周) -- 完善技术文档 -- 回归测试 -- Code Review - -**总计**: 约 4.5 周 - ---- - -### 7.2 详细任务清单 - -**阶段 1: 基础设施** -- [ ] 创建 `src/libslic3r/BoundaryValidator.hpp` -- [ ] 实现 `BuildVolumeBoundaryValidator` -- [ ] 扩展 `ConflictResult` 结构 -- [ ] 在 `Print` 类添加 `m_boundary_violations` -- [ ] 编写单元测试 `test_boundary_validator.cpp` - -**阶段 2: P0 修复(关键风险)** -- [ ] 实现 `BuildVolume::all_moves_inside()` -- [ ] 在 `GCodeViewer::load()` 中调用验证 -- [ ] 实现擦料塔位置验证(阻断性错误) -- [ ] 测试 P0 修复 - -**阶段 3: P1 修复** -- [ ] 修复螺旋抬升边界检查 -- [ ] 修复懒惰抬升边界检查 -- [ ] 修复 Skirt 边界检查 -- [ ] 修复 Brim 边界检查 -- [ ] 测试 P1 修复 - -**阶段 4: P2 修复和 GUI** -- [ ] 修复弧线路径验证 -- [ ] 修复支撑材料验证 -- [ ] 扩展 GUI 警告枚举 -- [ ] 实现警告文本生成 -- [ ] 可视化超限区域 - -**阶段 5: 测试和文档** -- [ ] 集成测试场景 -- [ ] 回归测试 -- [ ] 性能测试 -- [ ] 更新本技术文档 -- [ ] Code Review - ---- - -### 7.3 成功标准 - -1. ✅ 所有 8 个漏洞都已修复 -2. ✅ 单元测试覆盖率 > 85% -3. ✅ 集成测试通过率 100% -4. ✅ 性能影响 < 5% (切片时间) -5. ✅ 零严重回归 bug -6. ✅ Code Review 通过 -7. ✅ 技术文档完整 - ---- - -## 8. 参考资料 - -### 8.1 关键代码文件索引 - -| 功能 | 文件路径 | -|------|---------| -| 冲突检测 | `src/libslic3r/GCode/ConflictChecker.hpp/cpp` | -| 边界检测 | `src/libslic3r/BuildVolume.hpp/cpp` | -| G-code生成 | `src/libslic3r/GCode.hpp/cpp` | -| G-code写入 | `src/libslic3r/GCodeWriter.hpp/cpp` | -| G-code处理 | `src/libslic3r/GCode/GCodeProcessor.hpp/cpp` | -| 打印控制 | `src/libslic3r/Print.hpp/cpp` | -| GUI警告 | `src/slic3r/GUI/GLCanvas3D.cpp` | -| G-code预览 | `src/slic3r/GUI/GCodeViewer.cpp` | - -### 8.2 相关概念 - -- **BuildVolume**: 打印体积,定义打印边界 -- **ConflictChecker**: 冲突检测器,检测对象间路径冲突 -- **GCodeProcessor**: G-code 处理器,解析和验证 G-code -- **Spiral Lift**: 螺旋抬升,使用弧线命令抬升 Z 轴 -- **Lazy Lift**: 懒惰抬升,使用斜坡移动抬升 Z 轴 -- **Wipe Tower**: 擦料塔,多材料打印时的清料塔 -- **Skirt**: 裙边,打印对象周围的轮廓线 -- **Brim**: 边缘,增加附着力的边缘结构 - -### 8.3 相关 Issue 和 PR - -(待补充:相关的 GitHub Issue 和 Pull Request 链接) - ---- - -## 附录 A: 架构流程图 - -### A.1 当前验证流程 - -``` -用户操作 - │ - ├─→ 加载模型 - │ └─→ BuildVolume::object_state() ✓ - │ - ├─→ 移动模型 - │ └─→ Plater::update_print_volume_state() ✓ - │ - ├─→ 开始切片 - │ ├─→ Print::validate() ✓ - │ │ ├─ 打印高度检查 - │ │ ├─ 挤出头间隙检查 - │ │ └─ 擦料塔验证 ✗ (漏洞3) - │ │ - │ ├─→ 生成路径 - │ │ ├─ Skirt ✗ (漏洞4) - │ │ ├─ Brim ✗ (漏洞5) - │ │ └─ Support ✗ (漏洞6) - │ │ - │ └─→ 生成 G-code - │ ├─ Spiral Lift ✗ (漏洞1) - │ ├─ Lazy Lift ✗ (漏洞2) - │ └─ Arc Path ✗ (漏洞8) - │ - ├─→ 切片完成 - │ └─→ ConflictChecker::find_inter_of_lines_in_diff_objs() ✓ - │ - └─→ 加载预览 - └─→ BuildVolume::all_paths_inside() ✗ (漏洞7 - 不检查Travel) - -图例: ✓ 有检测 ✗ 无检测/不完整 -``` - -### A.2 优化后验证流程 - -``` -用户操作 - │ - ├─→ 加载模型 - │ └─→ BuildVolume::object_state() ✓ - │ - ├─→ 移动模型 - │ └─→ Plater::update_print_volume_state() ✓ - │ - ├─→ 开始切片 - │ ├─→ Print::validate() ✓✓ - │ │ ├─ 打印高度检查 - │ │ ├─ 挤出头间隙检查 - │ │ └─ 擦料塔边界验证 ✓✓ (NEW - 阻断性) - │ │ - │ ├─→ 生成路径 - │ │ ├─ Skirt + BoundaryValidator ✓✓ (NEW) - │ │ ├─ Brim + BoundaryValidator ✓✓ (NEW) - │ │ └─ Support + BoundaryValidator ✓✓ (NEW) - │ │ - │ └─→ 生成 G-code - │ ├─ Spiral Lift + Arc Validator ✓✓ (NEW) - │ ├─ Lazy Lift + Point Validator ✓✓ (NEW) - │ └─ Arc Path + Arc Validator ✓✓ (NEW) - │ - ├─→ 切片完成 - │ └─→ ConflictChecker::find_inter_of_lines_in_diff_objs() ✓ - │ - └─→ 加载预览 - └─→ GCodeViewer: 内联 Travel 检查 ✓✓ (NEW - 收集详细违规信息) - -图例: ✓ 原有检测 ✓✓ 新增/增强检测 - -> **实际实现说明**:Travel 检查采用内联实现而非 BuildVolume 方法,以便收集详细的违规信息(类型、方向、位置、距离)。见 `GCodeViewer.cpp:2427-2477`。 -``` - ---- - -## 附录 B: 测试场景矩阵 - -| 场景ID | 描述 | 触发条件 | 预期结果 | 优先级 | -|--------|------|----------|----------|--------| -| T1 | 大物体 + 大Skirt | 物体195x195, Skirt距离10mm, 床200x200 | 警告:Skirt超限 | P0 | -| T2 | 擦料塔在床外 | 手动设置塔位置(210, 210), 床200x200 | 错误:阻止切片 | P0 | -| T3 | 螺旋抬升超限 | 物体在(195,0), Spiral Lift, 床200x200 | 警告:降级为LazyLift | P1 | -| T4 | Travel移动超限 | 多物体,Travel路径超出边界 | 警告:Travel超限 | P0 | -| T5 | Brim超限 | 物体190x190 + 15mm Brim, 床200x200 | 警告:Brim超限 | P1 | -| T6 | 支撑超限 | 悬垂模型,支撑延伸超出床 | 警告:Support超限 | P2 | -| T7 | 弧线挤出超限 | ArcWelder + 边缘物体 | 警告:Arc超限 | P2 | -| T8 | 正常打印 | 所有路径在床内 | 无警告 | P0 | - ---- - -## 附录 C: API 变更清单 - -### 新增类 - -```cpp -// BoundaryValidator.hpp -class BoundaryValidator; -class BuildVolumeBoundaryValidator; - -// BoundaryValidator.hpp -enum class ViolationType { ... }; -struct BoundaryViolation { ... }; -``` - -### 新增方法 - -> **实际实现说明**:`BuildVolume::all_moves_inside()` 未添加。Travel 检查采用内联实现。 - -```cpp -// BuildVolume.hpp - 无变更 (设计方案未采纳) - -// GCodeViewer.cpp - 内联 Travel 检查实现 -// 位置: lines 2427-2477 -// 功能: 检查 Travel 移动并填充 boundary_violations - -// Print.hpp (如实施) -void Print::add_boundary_violation(const ConflictResult&); -const std::vector& Print::get_boundary_violations() const; -void Print::clear_boundary_violations(); - -// GCodeWriter.hpp (如实施) -void GCodeWriter::set_boundary_validator(...); -``` - -### 修改结构 - -```cpp -// GCodeProcessor.hpp -struct ConflictResult { - // 新增字段 - ConflictType conflict_type; - ViolationType violation_type; - Vec3d violation_position; - - // 新增静态方法 - static ConflictResult create_boundary_violation(...); -}; -``` - ---- - -**文档版本**: v1.0 -**最后更新**: 2026-01-15 -**作者**: Claude Code diff --git a/docs/gcode_boundary_final_implementation.md b/docs/gcode_boundary_final_implementation.md deleted file mode 100644 index 182829112b..0000000000 --- a/docs/gcode_boundary_final_implementation.md +++ /dev/null @@ -1,521 +0,0 @@ -# OrcaSlicer G-code边界检测 - 最终实施报告 - -**项目编号**: ORCA-2026-001-FINAL -**实施日期**: 2026-01-20 -**状态**: ✅ **全部完成** - ---- - -## 📋 完成情况总览 - -| 漏洞ID | 描述 | 优先级 | 状态 | 位置 | -|--------|------|--------|------|------| -| #1 | 螺旋抬升边界检查 | P1 | ✅ 完成 | GCodeWriter.cpp:557-620 | -| #2 | 懒惰抬升边界检查 | P1 | ✅ 完成 | GCodeWriter.cpp:621-666 | -| #3 | 擦料塔位置验证 | P0 | ✅ 完成 | Print.cpp:1290-1327 | -| #4 | Skirt边界验证 | P1 | ✅ 完成 | Print.cpp:2385-2502 | -| #5 | Brim边界验证 | P1 | ✅ 完成 | Brim.cpp:1745-1800 | -| #6 | 支撑材料边界验证 | P2 | ✅ 完成 | SupportMaterial.cpp:587-662 | -| #7 | Travel移动验证 | P0 | ✅ 完成 | GCodeViewer.cpp:2403-2450 | -| #8 | 弧线路径验证(G2/G3) | P2 | ✅ 完成 | Python工具 | - -**完成度**: 8/8 (100%) - ---- - -## 📂 修改文件清单 - -### 新增文件 (3个) - -| 文件 | 行数 | 说明 | -|------|------|------| -| `src/libslic3r/BoundaryValidator.hpp` | 149 | 边界验证器抽象接口 | -| `src/libslic3r/BoundaryValidator.cpp` | 211 | 边界验证器实现 | -| `tools/analyze_gcode_bounds.py` | ~500 | 命令行G-code检查工具 | -| `tools/gcode_boundary_checker_gui.py` | ~700 | GUI版G-code检查工具 | - -### 修改文件 (9个) - -| 文件 | 修改类型 | 主要变更 | -|------|----------|----------| -| `src/libslic3r/BuildVolume.hpp` | (无变更) | 保持原有接口 | -| `src/libslic3r/BuildVolume.cpp` | (无变更) | 保持原有实现 | -| `src/libslic3r/GCode/GCodeProcessor.hpp` | 结构扩展 | 扩展 `ConflictResult` | -| `src/libslic3r/Print.hpp` | 功能增强 | 添加边界超限追踪 | -| `src/libslic3r/Print.cpp` | 验证增强 | 擦料塔+Skirt边界检查 | -| `src/libslic3r/GCodeWriter.cpp` | 安全增强 | 螺旋/懒惰抬升边界检查与降级 | -| `src/libslic3r/Brim.cpp` | 验证增强 | Brim边界检查 | -| `src/libslic3r/Support/SupportMaterial.cpp` | 验证增强 | 支撑材料边界检查 | -| `src/slic3r/GUI/GCodeViewer.cpp` | 验证增强 | Travel移动边界检查 | -| `src/libslic3r/CMakeLists.txt` | 构建配置 | 添加新文件到构建 | - ---- - -## 🎯 各模块实现详情 - -### 1. 边界验证框架 (BoundaryValidator) - -**位置**: `src/libslic3r/BoundaryValidator.{hpp,cpp}` - -**功能**: -- ✅ 点验证 (`validate_point`) -- ✅ 线段验证 (`validate_line`) - 沿线采样10点 -- ✅ 弧线验证 (`validate_arc`) - 沿弧采样16点 -- ✅ 多边形验证 (`validate_polygon`) - 检查所有顶点 - -**支持的床类型**: -- Rectangle (矩形床) -- Circle (圆形床/Delta) -- Convex (凸多边形床) -- Custom (自定义床) - -**ViolationType 枚举**: -```cpp -enum class ViolationType { - SpiralLiftOutOfBounds, - LazyLiftOutOfBounds, - WipeTowerOutOfBounds, - SkirtOutOfBounds, - BrimOutOfBounds, - SupportOutOfBounds, - TravelMoveOutOfBounds, - ArcPathOutOfBounds -}; -``` - ---- - -### 2. Travel移动边界检查 (漏洞#7) - -**位置**: `src/slic3r/GUI/GCodeViewer.cpp:2427-2477` - -**实现方式**: 内联检查(不使用BuildVolume函数) - -**实现逻辑**: -```cpp -// 智能过滤:跳过初始化阶段 -// 1. 找到第一个挤出移动 (Z > 0.1mm) -// 2. 只检查此之后的Travel移动 -// 3. 使用 BedEpsilon 容差 -// 4. 直接在检查循环中收集 BoundaryViolationInfo -``` - -**为什么不用独立的 BuildVolume 函数**: -- 需要收集详细的违规信息(类型、方向、位置、距离) -- 简单的布尔返回值无法提供足够的诊断数据 -- 内联方式可以直接填充 `BoundaryViolationInfo` 结构 - -**关键特性**: -- ✅ 跳过G28/G29等初始化命令 -- ✅ 只检查Travel移动 (Extrude已有检查) -- ✅ 确定超限方向 (X_min/X_max/Y_min/Y_max) -- ✅ 记录位置、距离和Z高度 -- ✅ 填充到 `boundary_violations` 向量 - ---- - -### 3. 擦料塔位置验证 (漏洞#3) - -**位置**: `src/libslic3r/Print.cpp:1290-1327` - -**实现逻辑**: -```cpp -// 切片前验证擦料塔位置 -// 1. 计算擦料塔实际占用的四个角 (包括brim) -// 2. 检查是否在床边界内 -// 3. 如果超出,抛出阻断性错误 -``` - -**验证内容**: -- 擦料塔基础尺寸 (width × depth) -- 包含 brim 的总尺寸 -- 考虑板原点偏移 -- 四个角落全检查 - -**错误类型**: 阻断性错误(禁止切片继续) - ---- - -### 4. 螺旋/懒惰抬升边界检查 (漏洞#1, #2) - -**位置**: `src/libslic3r/GCodeWriter.cpp:557-666` - -**实现逻辑**: -```cpp -// 自动降级策略 -if (m_to_lift_type == LiftType::SpiralLift) { - radius = delta_z / (2 * PI * atan(travel_slope)); - - if (radius > MAX_SAFE_SPIRAL_RADIUS) { // 50mm - // 降级为 Lazy Lift - BOOST_LOG_TRIVIAL(warning) << "Spiral lift radius too large, downgrading"; - m_to_lift_type = LiftType::LazyLift; - } -} - -if (m_to_lift_type == LiftType::LazyLift) { - slope_distance = delta_z / tan(travel_slope); - - if (slope_distance > MAX_SAFE_SLOPE_DISTANCE) { // 100mm - // 降级为 Normal Lift - BOOST_LOG_TRIVIAL(warning) << "Lazy lift slope too long, downgrading"; - m_to_lift_type = LiftType::NormalLift; - } -} -``` - -**降级链条**: SpiralLift → LazyLift → NormalLift - -**安全阈值**: -- 螺旋抬升最大半径: 50mm -- 懒惰抬升最大斜坡距离: 100mm - ---- - -### 5. Skirt边界验证 (漏洞#4) - -**位置**: `src/libslic3r/Print.cpp:2385-2502` - -**实现逻辑**: -```cpp -// 在生成每个Skirt loop后验证 -for (size_t i = m_config.skirt_loops; i > 0; --i) { - // 生成Skirt loop - Polygon loop = offset(convex_hull, distance, ...); - - // 验证边界 - if (!validator.validate_polygon(loop, initial_layer_print_height)) { - // 记录超限但继续(不阻断) - this->add_boundary_violation(violation); - BOOST_LOG_TRIVIAL(warning) << "Skirt loop exceeds boundaries"; - } - - m_skirt.append(eloop); -} -``` - -**覆盖范围**: -- ✅ stCombined (统一Skirt) -- ✅ stPerObject (每个物体独立的Skirt) - -**处理方式**: 记录警告但继续执行 - ---- - -### 6. Brim边界验证 (漏洞#5) - -**位置**: `src/libslic3r/Brim.cpp:1745-1800` - -**实现逻辑**: -```cpp -// 为每个物体验证Brim区域 -for (auto iter = brimAreaMap.begin(); iter != brimAreaMap.end(); ++iter) { - for (const ExPolygon& expoly : iter->second) { - if (!validator.validate_polygon(expoly.contour, first_layer_height)) { - // 记录超限 - print_ptr->add_boundary_violation(violation); - BOOST_LOG_TRIVIAL(warning) << "Brim for object " << obj_name - << " exceeds build volume boundaries"; - } - } -} -``` - -**验证内容**: -- 物体Brim -- 支撑Brim - -**处理方式**: 记录警告但继续执行 - ---- - -### 7. 支撑材料边界验证 (漏洞#6) - -**位置**: `src/libslic3r/Support/SupportMaterial.cpp:587-662` - -**实现逻辑**: -```cpp -// 在支撑生成完成后验证 -for (const SupportLayer* layer : object.support_layers()) { - // 检查支撑挤出路径 - for (const ExtrusionEntity* entity : layer->support_fills.entities) { - if (const ExtrusionPath* path = dynamic_cast(entity)) { - if (!validator.validate_polygon(path->polyline, layer->print_z)) { - support_violations++; - } - } - } - - // 检查支撑多边形 - for (const ExPolygon& expoly : layer->lslices) { - if (!validator.validate_polygon(expoly.contour, layer->print_z)) { - support_violations++; - } - } -} -``` - -**验证内容**: -- 支撑挤出路径 (ExtrusionPath) -- 支撑循环 (ExtrusionLoop) -- 支撑多边形 (ExPolygon) -- 支撑孔洞多边形 - -**处理方式**: 记录警告但继续执行 - ---- - -### 8. G2/G3弧线路径验证 (漏洞#8) - -**位置**: Python工具 (`tools/analyze_gcode_bounds.py`, `tools/gcode_boundary_checker_gui.py`) - -**实现逻辑**: -```python -def _parse_arc(self, line_num, line, code_part, g_code, ...): - # 解析弧线参数 - i = float(i_match.group(1)) if i_match else 0.0 # X方向偏移 - j = float(j_match.group(1)) if j_match else 0.0 # Y方向偏移 - - # 计算圆心和半径 - center_x = start_x + i - center_y = start_y + j - radius = sqrt(i*i + j*j) - - # 计算起始和结束角度 - start_angle = atan2(start_y - center_y, start_x - center_x) - end_angle = atan2(end_y - center_y, end_x - center_x) - - # 沿弧线采样检查 (至少8点,或每5mm一个点) - num_samples = max(8, int(abs(angle_sweep) * radius / 5)) - - for n in range(num_samples + 1): - # 计算采样点位置 - sample_x = center_x + radius * cos(angle) - sample_y = center_y + radius * sin(angle) - - # 检查此点是否在边界内 - if not self._check_bounds(sample_pos): - # 记录超限 -``` - -**支持功能**: -- ✅ G2 顺时针弧线 -- ✅ G3 逆时针弧线 -- ✅ 完整圆弧 (无X/Y参数) -- ✅ 部分圆弧 (有X/Y参数) -- ✅ Z轴插值 -- ✅ 沿弧线多点采样 - ---- - -## 🔧 工具和辅助功能 - -### G-code边界检查工具 - -**GUI版本**: `tools/gcode_boundary_checker_gui.py` -- 图形界面操作 -- 文件浏览器选择G-code -- 快速预设常见床尺寸 -- 实时进度显示 -- 详细报告生成 - -**命令行版本**: `tools/analyze_gcode_bounds.py` -- 适合脚本集成 -- 批量处理 -- 支持所有床类型 - -**功能特性**: -- ✅ 检测Travel移动超限 -- ✅ 检测Extrude移动超限 -- ✅ 检测G2/G3弧线超限 -- ✅ 跳过纯Z移动 (避免误报) -- ✅ 按类型分类统计 -- ✅ 详细位置信息 - ---- - -## 📊 技术细节 - -### 容差设置 - -| 用途 | 容差值 | 说明 | -|------|--------|------| -| BedEpsilon | 3×EPSILON ≈ 3e-5 mm | 原始精度 | -| Travel检查 | BedEpsilon | 与原有检查一致 | -| Python工具 | 0.01 mm | 10微米精度 | -| 螺旋抬升半径限制 | 50 mm | 安全阈值 | -| 懒惰抬升距离限制 | 100 mm | 安全阈值 | - -### 性能考虑 - -| 功能 | 性能影响 | 说明 | -|------|----------|------| -| Travel检查 | < 2% | 仅在G-code预览时执行 | -| 擦料塔检查 | < 0.1% | 切片前一次性检查 | -| Skirt检查 | < 1% | 生成时并行检查 | -| Brim检查 | < 1% | 生成时并行检查 | -| 支撑检查 | < 2% | 生成完成后检查 | - -### 内存使用 - -- BoundaryValidator: 轻量级,仅持有BuildVolume引用 -- 违规记录: 每个违规约100字节 -- 预期影响: 对于典型切片 < 1MB - ---- - -## 🎨 设计模式 - -### 1. 策略模式 - -```cpp -// 抽象验证接口 -class BoundaryValidator { - virtual bool validate_point(const Vec3d& point) const = 0; - virtual bool validate_line(const Vec3d& from, const Vec3d& to) const = 0; - // ... -}; - -// 具体实现 -class BuildVolumeBoundaryValidator : public BoundaryValidator { - // 使用BuildVolume进行实际验证 -}; -``` - -### 2. 责任链模式 - -```cpp -// 抬升类型降级链 -SpiralLift → (超限) → LazyLift → (超限) → NormalLift -``` - -### 3. 观察者模式 - -```cpp -// 记录违规到Print对象 -print->add_boundary_violation(violation); -// GUI可监听并显示 -``` - ---- - -## 🧪 测试建议 - -### 单元测试 - -**BoundaryValidator测试**: -```cpp -TEST_CASE("BoundaryValidator - Rectangle bed") { - std::vector bed_shape = {{0,0}, {200,0}, {200,200}, {0,200}}; - BuildVolume bv(bed_shape, 250.0); - BuildVolumeBoundaryValidator validator(bv); - - REQUIRE(validator.validate_point(Vec3d(100, 100, 125))); // 内部 - REQUIRE_FALSE(validator.validate_point(Vec3d(250, 100, 125))); // 超出 -} -``` - -### 集成测试场景 - -| 场景 | 预期结果 | 优先级 | -|------|----------|--------| -| 标准正方体 | ✅ 无超限 | P0 | -| 大物体+Skirt | ⚠️ Skirt超限警告 | P1 | -| 擦料塔在床外 | ❌ 阻断性错误 | P0 | -| 螺旋抬升超限 | ⚠️ 降级+警告 | P1 | -| Travel移动超限 | ⚠️ 警告 | P0 | -| G2/G3弧线超限 | ⚠️ 检测并警告 | P2 | -| 支撑超限 | ⚠️ 警告 | P2 | - ---- - -## 📈 改进效果 - -### 修复前 vs 修复后 - -| 场景 | 修复前 | 修复后 | -|------|--------|--------| -| Travel移动超限 | ❌ 不检查 | ✅ 检测并警告 | -| 擦料塔位置错误 | ❌ 不检查 | ✅ 切片前阻断 | -| 螺旋抬升超限 | ❌ 可能撞机 | ✅ 自动降级 | -| Skirt/Brim超限 | ❌ 不检查 | ✅ 记录警告 | -| 支撑超限 | ❌ 不检查 | ✅ 记录警告 | -| G2/G3弧线超限 | ❌ 不检查 | ✅ 检测并警告 | - -### 用户影响 - -**安全性提升**: -- ✅ 防止打印头撞击边界 -- ✅ 防止擦料塔超出范围 -- ✅ 自动降级危险抬升 - -**可维护性提升**: -- ✅ 统一的验证框架 -- ✅ 清晰的违规报告 -- ✅ 详细的日志输出 - -**开发体验**: -- ✅ 可扩展的架构 -- ✅ 易于添加新验证 -- ✅ 完善的工具支持 - ---- - -## 🔮 后续优化建议 - -### 短期 (可选) - -1. **配置选项** - ```cpp - ConfigOptionBool strict_boundary_check {"strict_boundary_check", false}; - ConfigOptionFloat boundary_check_epsilon {"boundary_check_epsilon", 0.0}; - ``` - -2. **GUI可视化** - - 在3D预览中高亮超限路径 - - 显示违规位置标记 - -3. **更多测试** - - 扩展单元测试覆盖率 - - 添加回归测试 - -### 长期 (可选) - -1. **智能调整** - - 自动调整Skirt距离避免超限 - - 自动调整Brim宽度 - -2. **预测性检查** - - 切片前预判是否会超限 - - 提供调整建议 - ---- - -## 📝 总结 - -### 核心成就 - -✅ **8个漏洞全部修复** - 100%完成 -✅ **系统性防御** - 多层边界检查 -✅ **自动化降级** - 智能处理临界情况 -✅ **完善工具** - Python诊断工具 - -### 代码质量 - -- ✅ 遵循现有代码风格 -- ✅ 详细的注释和文档 -- ✅ 清晰的错误消息 -- ✅ 向后兼容 - -### 交付物 - -**代码文件**: 12个文件修改/新增 -**文档文件**: 3个Markdown文档 -**工具脚本**: 2个Python工具 -**总计**: ~2000行新增/修改代码 - ---- - -**项目状态**: ✅ **完成并可交付** -**最后更新**: 2026-01-20 -**版本**: v1.0-FINAL diff --git a/docs/gcode_boundary_optimization_implementation.md b/docs/gcode_boundary_optimization_implementation.md deleted file mode 100644 index 8b24c964d2..0000000000 --- a/docs/gcode_boundary_optimization_implementation.md +++ /dev/null @@ -1,772 +0,0 @@ -# OrcaSlicer G-code边界检测优化实施报告 - -**项目编号**: ORCA-2026-001-IMPL -**实施日期**: 2026-01-16 -**实施者**: Claude Code -**状态**: ⚠️ **已过时 - 中间实现文档** - -> **重要说明**:本文档描述的是中间实现状态。最终实现与本文档有重要差异: -> - `BuildVolume::all_moves_inside()` 方法已在后来被**删除** -> - Travel 检查改为**内联实现**在 `GCodeViewer.cpp:2427-2477` -> - 参见 `gcode_boundary_final_implementation.md` 了解最终实现状态 -> - 参见 `gcode_boundary_checking_optimization.md` 了解设计文档(已更新实际实现说明) - ---- - -## 目录 - -1. [实施概述](#1-实施概述) -2. [修改文件清单](#2-修改文件清单) -3. [详细修改说明](#3-详细修改说明) -4. [测试建议](#4-测试建议) -5. [后续工作](#5-后续工作) - ---- - -## 1. 实施概述 - -### 1.1 实施目标 - -根据技术文档 `gcode_boundary_checking_optimization.md` 中识别的8个关键漏洞,本次实施完成了以下核心修复: - -✅ **Phase 1: 基础设施** (已完成) -- 创建 BoundaryValidator 抽象验证框架 -- 扩展 ConflictResult 支持边界超限类型 -- 在 Print 类中添加边界超限追踪 - -✅ **Phase 2: P0 关键修复** (已完成) -- 修复漏洞 #7: Travel Moves 验证缺失 -- 修复漏洞 #3: 擦料塔位置验证缺失 - -✅ **Phase 3: P1 高优先级修复** (已完成) -- 修复漏洞 #1: 螺旋抬升边界检查 -- 修复漏洞 #2: 懒惰抬升边界检查 - -### 1.2 实施策略 - -采用**分层防御**策略: -1. **预防层**: 在路径生成时添加边界检查和自动降级 -2. **检测层**: 在 G-code 生成后验证所有移动(包括 Travel) -3. **验证层**: 在切片前验证关键组件(如擦料塔)位置 - ---- - -## 2. 修改文件清单 - -### 2.1 新增文件 - -| 文件路径 | 行数 | 说明 | -|---------|------|------| -| `src/libslic3r/BoundaryValidator.hpp` | 149 | 边界验证器抽象接口和实现类 | -| `src/libslic3r/BoundaryValidator.cpp` | 211 | 边界验证器实现代码 | -| `docs/gcode_boundary_optimization_implementation.md` | - | 本实施报告 | - -**总计新增代码**: ~360 行 - -### 2.2 修改文件 - -| 文件路径 | 修改类型 | 行数变化 | 说明 | -|---------|----------|----------|------| -| `src/libslic3r/BuildVolume.hpp` | 功能增强 | +3 | 新增 `all_moves_inside()` 方法声明 | -| `src/libslic3r/BuildVolume.cpp` | 功能增强 | +52 | 实现 `all_moves_inside()` 验证所有移动 | -| `src/libslic3r/GCode/GCodeProcessor.hpp` | 结构扩展 | +60 | 扩展 `ConflictResult` 支持边界超限 | -| `src/libslic3r/Print.hpp` | 功能增强 | +20 | 添加边界超限追踪方法 | -| `src/libslic3r/Print.cpp` | 验证增强 | +35 | 在 `validate()` 中添加擦料塔边界检查 | -| `src/libslic3r/GCodeWriter.cpp` | 安全增强 | +60 | 螺旋/懒惰抬升边界检查与降级 | -| `src/slic3r/GUI/GCodeViewer.cpp` | 验证增强 | +10 | 调用 `all_moves_inside()` 检测 Travel 移动 | -| `src/libslic3r/CMakeLists.txt` | 构建配置 | +2 | 添加 BoundaryValidator 到构建列表 | - -**总计修改**: 8个文件,~242 行新增/修改 - ---- - -## 3. 详细修改说明 - -### 3.1 Phase 1: 基础设施建设 - -#### 3.1.1 创建 BoundaryValidator 框架 - -**文件**: `src/libslic3r/BoundaryValidator.hpp` - -**设计理念**: -- 提供统一的边界验证接口,支持点、线、弧、多边形验证 -- 使用抽象基类设计,便于未来扩展不同验证策略 -- 基于 BuildVolume 的具体实现支持所有打印床类型 - -**核心接口**: -```cpp -class BoundaryValidator { -public: - enum class ViolationType { - SpiralLiftOutOfBounds, // 螺旋抬升超限 - LazyLiftOutOfBounds, // 懒惰抬升超限 - WipeTowerOutOfBounds, // 擦料塔超限 - SkirtOutOfBounds, // 裙边超限 - BrimOutOfBounds, // Brim 超限 - SupportOutOfBounds, // 支撑超限 - TravelMoveOutOfBounds, // Travel 移动超限 - ArcPathOutOfBounds // 弧线路径超限 - }; - - virtual bool validate_point(const Vec3d& point) const = 0; - virtual bool validate_line(const Vec3d& from, const Vec3d& to) const = 0; - virtual bool validate_arc(...) const = 0; - virtual bool validate_polygon(...) const = 0; -}; -``` - -**实现要点**: -1. **点验证**: 检查 XY 坐标和 Z 高度 -2. **线段验证**: 沿线段采样10个点验证 -3. **弧线验证**: 沿弧线采样16个点验证(防止弧线中段超限) -4. **多边形验证**: 检查所有顶点 - -**支持的打印床类型**: -- Rectangle (矩形) - 使用 BoundingBox 检测 -- Circle (圆形) - 使用距离平方检测 -- Convex/Custom (凸/自定义) - 使用点在多边形内检测 - -**代码位置**: `BoundaryValidator.cpp:47-117` - ---- - -#### 3.1.2 扩展 ConflictResult 结构 - -**文件**: `src/libslic3r/GCode/GCodeProcessor.hpp` - -**修改原因**: -- 原有 `ConflictResult` 只支持对象间冲突 -- 需要扩展以支持边界超限类型 - -**新增字段**: -```cpp -struct ConflictResult { - // 原有字段 - std::string _objName1, _objName2; - double _height; - const void *_obj1, *_obj2; - int layer; - - // 新增字段 - enum class ConflictType { - ObjectCollision, // 原有: 对象间冲突 - BoundaryViolation // 新增: 边界超限 - }; - - ConflictType conflict_type = ConflictType::ObjectCollision; - int violation_type_int = -1; // 存储 ViolationType - Vec3d violation_position; // 超限位置 - - // 新增静态工厂方法 - static ConflictResult create_boundary_violation(...); - - // 新增辅助方法 - bool is_boundary_violation() const; - bool is_object_collision() const; -}; -``` - -**设计考虑**: -- 保持向后兼容:默认构造仍为 `ObjectCollision` -- 使用 `int` 存储枚举避免跨模块依赖问题 -- 提供类型检查辅助方法 - -**代码位置**: `GCodeProcessor.hpp:110-167` - ---- - -#### 3.1.3 在 Print 类添加边界超限追踪 - -**文件**: `src/libslic3r/Print.hpp`, `src/libslic3r/Print.cpp` - -**新增成员变量**: -```cpp -class Print { - ConflictResultOpt m_conflict_result; // 原有 - std::vector m_boundary_violations; // 新增 -}; -``` - -**新增方法**: -```cpp -void add_boundary_violation(const ConflictResult& violation); -const std::vector& get_boundary_violations() const; -void clear_boundary_violations(); -bool has_boundary_violations() const; -``` - -**用途**: -- 收集切片过程中发现的所有边界超限 -- 供 GUI 显示警告和可视化 -- 支持批量检测和报告 - -**代码位置**: -- 声明: `Print.hpp:973-988` -- 定义: `Print.hpp:1065` (成员变量) - ---- - -### 3.2 Phase 2: P0 关键修复 - -#### 3.2.1 修复漏洞 #7: Travel Moves 验证缺失 ⭐⭐⭐⭐⭐ - -**问题描述**: -- 原有 `all_paths_inside()` 只验证挤出移动,忽略 Travel 移动 -- Travel 移动超出边界可能导致打印头撞击 - -**修复方案**: - -**1) 新增 `BuildVolume::all_moves_inside()` 方法** - -**文件**: `src/libslic3r/BuildVolume.hpp`, `BuildVolume.cpp` - -**原有代码逻辑**: -```cpp -// BuildVolume.cpp:330 - 原有的 all_paths_inside() -auto move_valid = [](const GCodeProcessorResult::MoveVertex &move) { - return move.type == EMoveType::Extrude && // 只检查挤出! - move.extrusion_role != erCustom && - move.width != 0.f && - move.height != 0.f; -}; -``` - -**新增代码逻辑**: -```cpp -// BuildVolume.cpp:371 - 新增的 all_moves_inside() -auto move_significant = [](const GCodeProcessorResult::MoveVertex &move) { - return move.type == EMoveType::Extrude || - move.type == EMoveType::Travel; // 同时检查 Travel! -}; -``` - -**实现细节**: -- 验证所有 `Extrude` 和 `Travel` 类型移动 -- 排除 `Retract` 和 `Unretract`(Z轴不移动) -- 支持 Rectangle, Circle, Convex, Custom 所有打印床类型 -- 逐点验证每个移动的终点位置 - -**2) 在 GCodeViewer 中调用验证** - -**文件**: `src/slic3r/GUI/GCodeViewer.cpp` - -**修改位置**: 行 2398-2433 - -**调用逻辑**: -```cpp -// 先检查挤出路径(原有) -m_contained_in_bed = build_volume.all_paths_inside(gcode_result, m_paths_bounding_box); - -// 新增: 同时检查 Travel 移动 -if (m_contained_in_bed) { - bool all_moves_valid = build_volume.all_moves_inside(gcode_result, m_paths_bounding_box); - if (!all_moves_valid) { - m_contained_in_bed = false; - BOOST_LOG_TRIVIAL(warning) << "Travel moves detected outside build volume boundaries"; - } -} -``` - -**效果**: -- ✅ 检测所有 Travel 移动超限 -- ✅ 设置 `toolpath_outside` 标志触发 GUI 警告 -- ✅ 防止打印头撞击边界 - -**影响范围**: **所有打印**(系统性修复) - -**代码位置**: -- 方法声明: `BuildVolume.hpp:96` -- 方法实现: `BuildVolume.cpp:371-419` -- 调用点: `GCodeViewer.cpp:2405-2411` - ---- - -#### 3.2.2 修复漏洞 #3: 擦料塔位置验证缺失 ⭐⭐⭐⭐⭐ - -**问题描述**: -- 擦料塔(Prime Tower)位置由用户手动设置 -- 原代码只检查与其他对象的碰撞,不检查是否超出床边界 -- 包括 brim 的实际占用面积可能远大于配置宽度 - -**修复方案**: - -**文件**: `src/libslic3r/Print.cpp` - -**修改位置**: `Print::validate()` 方法,行 1289-1323 - -**实现代码**: -```cpp -// 在擦料塔验证段末尾添加(has_wipe_tower() 块内) -{ - const size_t plate_index = this->get_plate_index(); - const Vec3d plate_origin = this->get_plate_origin(); - const float x = m_config.wipe_tower_x.get_at(plate_index) + plate_origin(0); - const float y = m_config.wipe_tower_y.get_at(plate_index) + plate_origin(1); - const float width = m_config.prime_tower_width.value; - const float brim_width = m_config.prime_tower_brim_width.value; - const float depth = this->wipe_tower_data(extruders.size()).depth; - - // 创建床边界框 - BoundingBoxf bed_bbox; - for (const Vec2d& pt : m_config.printable_area.values) { - bed_bbox.merge(pt); - } - - bool tower_outside = false; - // 检查所有四个角(包括 brim) - if (x - brim_width < bed_bbox.min.x() || - x + width + brim_width > bed_bbox.max.x() || - y - brim_width < bed_bbox.min.y() || - y + depth + brim_width > bed_bbox.max.y()) { - tower_outside = true; - } - - if (tower_outside) { - const float total_width = width + 2 * brim_width; - const float total_depth = depth + 2 * brim_width; - return StringObjectException{ - Slic3r::format(_u8L("The prime tower at position (%.2f, %.2f) " - "with dimensions %.2f x %.2f mm " - "(including %.2f mm brim) exceeds the bed boundaries. " - "Please adjust the prime tower position in the configuration."), - x, y, total_width, total_depth, brim_width), - nullptr, - "wipe_tower_x" - }; - } -} -``` - -**验证内容**: -- ✅ 擦料塔基础尺寸 (width × depth) -- ✅ 包含 brim 的总尺寸 (width + 2×brim_width) × (depth + 2×brim_width) -- ✅ 四个角落是否在床边界内 -- ✅ 考虑板原点偏移 (plate_origin) - -**错误类型**: **阻断性错误** -- 不允许切片继续 -- 用户必须调整擦料塔位置 -- 提供清晰的错误信息和修复建议 - -**效果**: -- ✅ 防止擦料塔超出边界导致撞机 -- ✅ 提前发现问题,避免打印失败 -- ✅ 提供详细的错误位置和尺寸信息 - -**影响范围**: 所有使用擦料塔的多材料打印 - -**代码位置**: `Print.cpp:1289-1323` - ---- - -### 3.3 Phase 3: P1 高优先级修复 - -#### 3.3.1 修复漏洞 #1 & #2: 螺旋/懒惰抬升边界检查 ⭐⭐⭐⭐ - -**问题描述**: - -**漏洞 #1 - 螺旋抬升 (Spiral Lift)**: -- 使用 G2/G3 弧线命令抬升 Z 轴 -- 弧线半径计算: `radius = delta_z / (2π × tan(slope))` -- 大 Z 抬升 → 大半径 → 可能超出边界 -- 原代码有 TODO 注释但未实现 - -**漏洞 #2 - 懒惰抬升 (Lazy Lift)**: -- 沿斜坡移动抬升 Z 轴 -- 斜坡距离计算: `distance = delta_z / tan(slope)` -- 长距离移动 → 大斜坡延伸 → 可能超出边界 - -**修复方案**: 自动降级策略 - -**文件**: `src/libslic3r/GCodeWriter.cpp` - -**修改位置**: `GCodeWriter::travel_to_xyz()` 方法,行 543-602 - -**实现逻辑**: - -```cpp -if (delta(2) > 0 && delta_no_z.norm() != 0.0f) { - // 螺旋抬升检查 - if (m_to_lift_type == LiftType::SpiralLift && this->is_current_position_clear()) { - double radius = delta(2) / (2 * PI * atan(this->extruder()->travel_slope())); - - constexpr double MAX_SAFE_SPIRAL_RADIUS = 50.0; // mm - - if (radius > MAX_SAFE_SPIRAL_RADIUS) { - BOOST_LOG_TRIVIAL(warning) << "Spiral lift radius (" << radius - << " mm) exceeds safe limit, downgrading to lazy lift"; - m_to_lift_type = LiftType::LazyLift; // 降级 - } - else { - // 执行螺旋抬升 - Vec2d ij_offset = radius * delta_no_z.normalized(); - ij_offset = { -ij_offset(1), ij_offset(0) }; - slop_move = this->_spiral_travel_to_z(target(2), ij_offset, "spiral lift Z"); - } - } - - // 懒惰抬升检查 - if (m_to_lift_type == LiftType::LazyLift && - this->is_current_position_clear() && - atan2(delta(2), delta_no_z.norm()) < this->extruder()->travel_slope()) { - - Vec2d temp = delta_no_z.normalized() * delta(2) / tan(this->extruder()->travel_slope()); - Vec3d slope_top_point = Vec3d(temp(0), temp(1), delta(2)) + source; - - constexpr double MAX_SAFE_SLOPE_DISTANCE = 100.0; // mm - double slope_distance = temp.norm(); - - if (slope_distance > MAX_SAFE_SLOPE_DISTANCE) { - BOOST_LOG_TRIVIAL(warning) << "Lazy lift slope distance (" << slope_distance - << " mm) exceeds safe limit, downgrading to normal lift"; - m_to_lift_type = LiftType::NormalLift; // 降级 - } - else { - // 执行懒惰抬升 - GCodeG1Formatter w0; - w0.emit_xyz(slope_top_point); - w0.emit_f(travel_speed * 60.0); - w0.emit_comment(GCodeWriter::full_gcode_comment, comment); - slop_move = w0.string(); - } - } - - // 正常抬升(兜底) - if (m_to_lift_type == LiftType::NormalLift) { - slop_move = _travel_to_z(target.z(), "normal lift Z"); - } -} -``` - -**安全阈值设定**: -- **螺旋抬升**: 最大半径 50mm - - 典型200×200mm床: 对角线 ~282mm,半径50mm是安全的 - - 超过此值可能接近床边缘 - -- **懒惰抬升**: 最大斜坡距离 100mm - - 大多数打印床尺寸下安全 - - 防止极端长距离移动 - -**降级策略**: -1. SpiralLift → LazyLift → NormalLift -2. 逐级降级确保安全 -3. 记录警告日志便于调试 - -**效果**: -- ✅ 自动检测并防止超限 -- ✅ 保持功能可用性(降级而非禁用) -- ✅ 提供日志记录便于诊断 -- ✅ 无需用户干预 - -**影响范围**: 使用螺旋/懒惰抬升的打印 - -**代码位置**: `GCodeWriter.cpp:545-602` - ---- - -## 4. 测试建议 - -### 4.1 单元测试场景 - -#### 4.1.1 BoundaryValidator 测试 - -**测试文件**: `tests/libslic3r/test_boundary_validator.cpp` (建议创建) - -**测试用例**: - -```cpp -TEST_CASE("BoundaryValidator - Rectangle bed", "[boundary]") { - std::vector bed_shape = {{0,0}, {200,0}, {200,200}, {0,200}}; - BuildVolume bv(bed_shape, 250.0); - BuildVolumeBoundaryValidator validator(bv); - - // 测试点验证 - REQUIRE(validator.validate_point(Vec3d(100, 100, 125))); // 中心点 - REQUIRE_FALSE(validator.validate_point(Vec3d(250, 100, 125))); // 超出X - REQUIRE_FALSE(validator.validate_point(Vec3d(100, 100, 300))); // 超出Z - - // 测试线段验证 - REQUIRE(validator.validate_line(Vec3d(50,50,10), Vec3d(150,150,10))); - REQUIRE_FALSE(validator.validate_line(Vec3d(50,50,10), Vec3d(250,250,10))); - - // 测试弧线验证 - // ... -} - -TEST_CASE("BoundaryValidator - Circle bed", "[boundary]") { - // Delta 打印机测试 - // ... -} -``` - -#### 4.1.2 Travel Moves 验证测试 - -**测试场景**: -```cpp -TEST_CASE("BuildVolume - all_moves_inside includes Travel", "[buildvolume]") { - // 创建包含 Travel 移动的 GCodeProcessorResult - GCodeProcessorResult result; - - // 添加合法的 Travel 移动 - result.moves.push_back({.type = EMoveType::Travel, .position = {100,100,50}}); - REQUIRE(bv.all_moves_inside(result, bbox)); - - // 添加超限的 Travel 移动 - result.moves.push_back({.type = EMoveType::Travel, .position = {250,100,50}}); - REQUIRE_FALSE(bv.all_moves_inside(result, bbox)); -} -``` - -### 4.2 集成测试场景 - -#### 场景 T1: 大物体 + 大 Skirt (P0) -- **设置**: 物体 195×195mm, Skirt 距离 10mm, 床 200×200mm -- **预期**: 警告 Skirt 超限(尚未实现此修复) -- **优先级**: P1 - -#### 场景 T2: 擦料塔在床外 (P0) ✅ -- **设置**: 手动设置塔位置 (210, 210), 床 200×200mm -- **预期**: 阻断性错误,禁止切片 -- **验证**: `Print::validate()` 返回错误 -- **状态**: ✅ 已实现 - -#### 场景 T3: 螺旋抬升超限 (P1) ✅ -- **设置**: 物体在 (195, 0), 启用 Spiral Lift, 大 Z 抬升 -- **预期**: 自动降级为 Lazy Lift,日志警告 -- **验证**: 检查 G-code 中无 G2/G3 命令 -- **状态**: ✅ 已实现 - -#### 场景 T4: Travel 移动超限 (P0) ✅ -- **设置**: 多物体,Travel 路径超出边界 -- **预期**: `toolpath_outside` 标志设置,GUI 显示警告 -- **验证**: GCodeViewer 显示橙色警告 -- **状态**: ✅ 已实现 - -### 4.3 回归测试 - -**关键检查点**: -1. ✅ 正常打印不受影响(无误报) -2. ✅ 性能影响 < 5% (边界检查开销) -3. ✅ 原有冲突检测功能正常工作 -4. ✅ GUI 警告显示正确 - -### 4.4 性能测试 - -**测试方法**: -```bash -# 测试大型模型切片时间 -# Before: xxx seconds -# After: xxx seconds (+X%) -``` - -**预期性能影响**: -- `all_moves_inside()`: +1-2% (逐点检查) -- 擦料塔验证: +0.1% (切片前一次性检查) -- 抬升降级: 0% (仅在触发时) - ---- - -## 5. 后续工作 - -### 5.1 未完成的 P1/P2 修复 - -根据原技术文档,以下漏洞尚未修复: - -#### 漏洞 #4: Skirt 超限 (P1) ⏳ -**位置**: `src/libslic3r/Print.cpp:2338-2357` -**修复方案**: 在 Skirt 生成后添加边界验证 -**优先级**: 高 - -#### 漏洞 #5: Brim 超限 (P1) ⏳ -**位置**: `src/libslic3r/Brim.cpp` -**修复方案**: 在 Brim 生成后添加边界验证 -**优先级**: 高 - -#### 漏洞 #6: 支撑材料超限 (P2) ⏳ -**位置**: `src/libslic3r/SupportMaterial.cpp`, `src/libslic3r/Support/TreeSupport.cpp` -**修复方案**: 在支撑生成时限制边界 -**优先级**: 中 - -#### 漏洞 #8: 弧线路径超限 (P2) ⏳ -**位置**: `src/libslic3r/GCodeWriter.cpp:673-691, 732-752` -**修复方案**: 在 `_spiral_travel_to_z()` 和 `extrude_arc_to_xy()` 中使用 `validate_arc()` -**优先级**: 中 - -### 5.2 GUI 增强 - -#### 5.2.1 可视化边界超限 ⏳ -- 在 3D 预览中高亮显示超限路径 -- 使用红色标记超限的 Travel 移动 -- 显示擦料塔边界框 - -#### 5.2.2 警告消息改进 ⏳ -- 扩展 `GLCanvas3D::EWarning` 枚举 -- 添加边界超限专用警告类型 -- 提供详细的超限位置信息 - -### 5.3 配置选项 ⏳ - -建议添加高级配置: -```cpp -// PrintConfig 中添加 -class PrintConfig { - ConfigOptionBool strict_boundary_check {"strict_boundary_check", false}; - ConfigOptionFloat boundary_check_epsilon {"boundary_check_epsilon", 3.0}; -}; -``` - -**用途**: -- `strict_boundary_check`: 将警告升级为错误 -- `boundary_check_epsilon`: 调整边界容差 - -### 5.4 文档和测试 ⏳ - -- [ ] 完善单元测试覆盖率至 >85% -- [ ] 创建集成测试套件 -- [ ] 编写用户文档说明新警告 -- [ ] 更新开发者文档 - ---- - -## 6. 总结 - -### 6.1 完成情况 - -| 阶段 | 内容 | 状态 | 完成度 | -|------|------|------|--------| -| Phase 1 | 基础设施建设 | ✅ 完成 | 100% | -| Phase 2 | P0 关键修复 | ✅ 完成 | 100% | -| Phase 3 | P1 高优先级修复 (部分) | ✅ 完成 | 50% | -| Phase 4 | P2 修复 | ⏳ 未开始 | 0% | -| Phase 5 | GUI 增强 | ⏳ 未开始 | 0% | -| 总体 | - | 🟡 部分完成 | **60%** | - -### 6.2 关键成果 - -✅ **系统性修复**: -- Travel Moves 验证缺失(影响最广的漏洞) -- 擦料塔位置验证缺失(高风险漏洞) - -✅ **安全增强**: -- 螺旋/懒惰抬升自动降级机制 -- 多层防御策略 - -✅ **代码质量**: -- 新增 ~360 行高质量代码 -- 修改/增强 ~242 行现有代码 -- 编译通过,无警告 - -✅ **可扩展性**: -- BoundaryValidator 框架便于未来扩展 -- ConflictResult 扩展支持更多验证类型 - -### 6.3 风险评估 - -**技术风险**: 🟢 低 -- 所有修改已编译通过 -- 向后兼容现有功能 -- 采用防御性编程策略 - -**性能风险**: 🟢 低 -- 预期性能影响 < 5% -- 边界检查使用高效算法 -- 仅在必要时触发验证 - -**兼容性风险**: 🟢 低 -- 不影响现有 G-code 输出 -- 仅增加验证和警告 -- 不改变切片算法 - -### 6.4 建议后续步骤 - -**立即行动**: -1. ✅ 编译验证 - 已完成 -2. 🔄 单元测试 - 进行中 -3. 🔄 集成测试 - 待开始 - -**短期目标** (1-2周): -1. 完成 Skirt/Brim 边界验证 (P1) -2. 添加基础单元测试 -3. 进行回归测试 - -**中期目标** (1个月): -1. 完成所有 P2 修复 -2. GUI 可视化增强 -3. 性能优化 - ---- - -## 附录 - -### A. 修改的代码行统计 - -``` -新增文件: - BoundaryValidator.hpp 149 lines - BoundaryValidator.cpp 211 lines - 实施文档 本文档 - -修改文件: - BuildVolume.hpp +3 lines - BuildVolume.cpp +52 lines - GCodeProcessor.hpp +60 lines - Print.hpp +20 lines - Print.cpp +35 lines - GCodeWriter.cpp +60 lines - GCodeViewer.cpp +10 lines - CMakeLists.txt +2 lines - -总计: 新增 ~360 行, 修改 ~242 行 -``` - -### B. 编译验证 - -``` -编译器: MSVC 17.11 (Visual Studio 2022) -配置: Release x64 -结果: ✅ 成功 -警告: 0 -错误: 0 -``` - -### C. Git 提交建议 - -```bash -git add src/libslic3r/BoundaryValidator.* -git add src/libslic3r/BuildVolume.* -git add src/libslic3r/Print.* -git add src/libslic3r/GCode/GCodeProcessor.hpp -git add src/libslic3r/GCodeWriter.cpp -git add src/slic3r/GUI/GCodeViewer.cpp -git add src/libslic3r/CMakeLists.txt -git add docs/gcode_boundary_optimization_implementation.md - -git commit -m "feat: Implement G-code boundary checking optimizations - -Fixes critical vulnerabilities in boundary validation: - -- ✅ P0: Add Travel moves validation (system-wide fix) -- ✅ P0: Add wipe tower position validation (blocking error) -- ✅ P1: Add spiral/lazy lift boundary check with auto-downgrade -- ✅ Infrastructure: Create BoundaryValidator framework -- ✅ Infrastructure: Extend ConflictResult for boundary violations - -Details: -- New files: BoundaryValidator.hpp/cpp (~360 lines) -- Modified: 8 files (~242 lines) -- Compilation: ✅ Passed with no warnings -- Performance impact: < 5% expected - -Related: ORCA-2026-001 -Documentation: docs/gcode_boundary_optimization_implementation.md -" -``` - ---- - -**文档结束** - -**实施者**: Claude Code -**审核**: 待用户审核 -**版本**: v1.0 -**日期**: 2026-01-16 diff --git a/docs/release_risk_assessment.md b/docs/release_risk_assessment.md deleted file mode 100644 index aee8d1554d..0000000000 --- a/docs/release_risk_assessment.md +++ /dev/null @@ -1,619 +0,0 @@ -# OrcaSlicer G-code边界检测 - 发版风险评估与测试指南 - -**文档版本**: v1.0-RISK -**创建日期**: 2026-01-20 -**项目编号**: ORCA-2026-001-RELEASE -**风险等级**: 🟡 **中等风险** (需要充分测试) - ---- - -## 📋 执行摘要 - -### 核心变更 -本次实施为OrcaSlicer添加了**完整的边界检测系统**,包括8个漏洞修复,共涉及12个文件,约2000行新增/修改代码。 - -### 风险评级 -| 维度 | 风险等级 | 说明 | -|------|----------|------| -| **功能影响** | 🟡 中 | 改变边界检查行为,可能影响部分切片结果 | -| **性能影响** | 🟢 低 | 性能影响 < 5%,用户无感知 | -| **兼容性** | 🟡 中 | 可能影响现有打印配置(边缘打印) | -| **回滚难度** | 🟢 低 | 修改集中,可快速回滚 | -| **测试覆盖** | 🟡 中 | 需要新增测试用例 | - -### 建议措施 -✅ **推荐发布** - 建议在充分测试后发布 -⚠️ **必须测试** - 边界打印场景需要验证 -📝 **发布说明** - 需要在更新日志中说明变更 - ---- - -## 📂 修改文件清单 - -### 核心代码修改 (9个文件) - -| 文件 | 修改类型 | 代码量 | 风险等级 | 说明 | -|------|----------|--------|----------|------| -| `src/libslic3r/BuildVolume.hpp` | 新增方法 | +3 | 🟢 低 | 新增接口声明 | -| `src/libslic3r/BuildVolume.cpp` | 新增方法 | +52 | 🟢 低 | Travel检查实现 | -| `src/libslic3r/GCode/GCodeProcessor.hpp` | 结构扩展 | +60 | 🟢 低 | 扩展ConflictResult | -| `src/libslic3r/Print.hpp` | 新增方法 | +20 | 🟢 低 | 违规追踪接口 | -| `src/libslic3r/Print.cpp` | 验证增强 | +70 | 🟡 **中** | 擦料塔+Skirt检查 | -| `src/libslic3r/GCodeWriter.cpp` | 逻辑增强 | +130 | 🟡 **中** | 螺旋/懒惰抬升降级 | -| `src/libslic3r/Brim.cpp` | 验证增强 | +60 | 🟡 **中** | Brim边界检查 | -| `src/libslic3r/Support/SupportMaterial.cpp` | 验证增强 | +80 | 🟡 **中** | 支撑边界检查 | -| `src/slic3r/GUI/GCodeViewer.cpp` | 验证增强 | +50 | 🟡 **中** | Travel移动检查 | - -### 新增文件 (4个) - -| 文件 | 行数 | 用途 | 风险 | -|------|------|------|------| -| `src/libslic3r/BoundaryValidator.hpp` | 149 | 验证框架接口 | 🟢 低 | -| `src/libslic3r/BoundaryValidator.cpp` | 211 | 验证框架实现 | 🟢 低 | -| `tools/analyze_gcode_bounds.py` | ~500 | 命令行诊断工具 | 🟢 无 | -| `tools/gcode_boundary_checker_gui.py` | ~700 | GUI诊断工具 | 🟢 无 | - -**总计**: 12个文件,~2000行代码 - ---- - -## 🔍 功能变更详解 - -### 变更1: Travel移动边界检查 (影响:🟡 中) - -**位置**: `src/slic3r/GUI/GCodeViewer.cpp:2403-2450` - -**变更内容**: -```cpp -// 之前:只检查Extrude移动 -m_contained_in_bed = build_volume.all_paths_inside(gcode_result, m_paths_bounding_box); - -// 之后:同时检查Travel移动 -if (m_contained_in_bed) { - bool all_moves_valid = build_volume.all_moves_inside(gcode_result, ...); - if (!all_moves_valid) { - m_contained_in_bed = false; // 设置超限标志 - } -} -``` - -**影响范围**: -- ✅ 所有切片的G-code预览 -- ✅ 会检测到之前被忽略的Travel移动超限 - -**风险场景**: -- ⚠️ 之前允许的边缘Travel移动现在会报错 -- ⚠️ 可能影响:边缘擦料塔、边缘物体的大跨度移动 - -**用户可见变化**: -- G-code预览可能显示橙色"toolpath_outside"警告 -- 右下角可能显示"部分路径超出打印床"提示 - -**缓解措施**: -- 智能过滤:跳过G28/G29初始化阶段 -- 使用BedEpsilon容差(3e-5mm,极小) -- 只警告,不阻断切片 - ---- - -### 变更2: 擦料塔位置验证 (影响:🟡 高) - -**位置**: `src/libslic3r/Print.cpp:1290-1327` - -**变更内容**: -```cpp -// 之前:不验证擦料塔位置 - -// 之后:切片前严格验证 -if (擦料塔超出边界) { - return StringObjectException{错误信息}; // 阻断切片 -} -``` - -**影响范围**: -- ✅ 所有使用擦料塔的多材料打印 -- ✅ 所有使用擦料塔的支撑/界面打印 - -**风险场景**: -- ❌ **阻断性**: 如果擦料塔位置设置在床外,切片会**完全失败** -- ⚠️ 用户之前可能设置了超出边界的擦料塔位置,现在无法切片 - -**用户可见变化**: -- 错误提示:"The prime tower at position (x, y) with dimensions W×D mm (including brim) exceeds the bed boundaries" - -**缓解措施**: -- 错误信息清晰,提供具体位置和尺寸 -- 建议用户调整擦料塔位置 - ---- - -### 变更3: 螺旋/懒惰抬升自动降级 (影响:🟡 中) - -**位置**: `src/libslic3r/GCodeWriter.cpp:557-663` - -**变更内容**: -```cpp -// 降级链条: -SpiralLift → [弧线超限] → LazyLift → [斜坡超限] → NormalLift -``` - -**影响范围**: -- ✅ 所有启用"螺旋抬升"的切片 -- ✅ 所有启用"懒惰抬升"的切片 -- ✅ 主要影响边缘区域的抬升行为 - -**风险场景**: -- ⚠️ 边缘区域的抬升方式可能改变 -- ⚠️ 可能轻微影响打印质量(抬升方式不同) -- ✅ 但不会超限撞机 - -**用户可见变化**: -- 一般情况:无任何变化 -- 边缘打印:日志中可能出现降级警告 -- 极端情况:抬升路径改变(更安全) - -**缓解措施**: -- 逐级降级,保持功能可用 -- 详细日志记录 -- 只在必要时降级 - ---- - -### 变更4: Skirt/Brim/支撑边界检查 (影响:🟡 中) - -**位置**: -- Skirt: `Print.cpp:2385-2502` -- Brim: `Brim.cpp:1745-1800` -- 支撑: `SupportMaterial.cpp:587-662` - -**变更内容**: -```cpp -// 之前:生成后不验证边界 - -// 之后:生成时验证并记录违规 -if (!validator.validate_polygon(geometry, z_height)) { - print->add_boundary_violation(violation); - BOOST_LOG_TRIVIAL(warning) << "... exceeds boundaries"; -} -``` - -**影响范围**: -- ✅ 所有使用Skirt的打印 -- ✅ 所有使用Brim的打印 -- ✅ 所有使用支撑的打印 - -**风险场景**: -- ⚠️ 大尺寸Skirt/Brim可能被记录为违规 -- ⚠️ 支撑超出边界会被记录 -- ✅ 但不阻断切片,只记录警告 - -**用户可见变化**: -- 一般情况:无任何变化 -- 违规情况:日志中有警告 -- 未来版本可能显示警告UI - -**缓解措施**: -- 非阻断性(不停止切片) -- 只记录违规,供后续分析 -- 可通过配置调整Skirt/Brim参数避免 - ---- - -## ⚠️ 风险分析矩阵 - -### 高风险场景 - -| 场景 | 风险等级 | 概率 | 影响 | 缓解措施 | -|------|----------|------|------|----------| -| 擦料塔位置超出边界 | 🔴 高 | 中 | **无法切片** | 清晰错误信息,引导调整位置 | -| 边缘物体+螺旋抬升 | 🟡 中 | 高 | 抬升方式改变 | 自动降级,保持安全 | -| 大尺寸Skirt | 🟡 中 | 中 | 记录违规警告 | 非阻断,可调整参数 | -| 边缘物体的大跨度Travel | 🟡 中 | 低 | 可能报超限 | 智能过滤初始化阶段 | - -### 低风险场景 - -| 场景 | 风险等级 | 说明 | -|------|----------|------| -| 标准物体(床中心) | 🟢 低 | 无影响 | -| 小物体 | 🟢 低 | 无影响 | -| 正常参数设置 | 🟢 低 | 无影响 | - ---- - -## 🧪 测试用例设计 - -### P0 - 必须测试 (Critical) - -#### TC01: 标准打印 - 无风险验证 -**目的**: 确保正常打印不受影响 - -**步骤**: -1. 加载标准测试模型(如20mm立方体) -2. 放置在床中心位置 -3. 使用默认参数切片 -4. 验证: - - ✅ 切片成功,无错误 - - ✅ 警告日志数量为0(或只有正常信息) - - ✅ G-code预览显示正常 - - ✅ 打印时间无显著变化 - -**预期结果**: 完全正常,无任何影响 - ---- - -#### TC02: 擦料塔位置验证 - 阻断性测试 -**目的**: 验证超出边界的擦料塔被正确阻止 - -**步骤**: -1. 创建双材料打印配置 -2. **手动设置擦料塔位置在床外**(如 X=300, Y=300,对于200×200床) -3. 尝试切片 - -**预期结果**: -- ❌ 切片**失败**,显示错误: - ``` - The prime tower at position (300.00, 300.00) with dimensions - XX × XX mm exceeds the bed boundaries. - Please adjust the prime tower position. - ``` - -**验证点**: -- ✅ 错误信息清晰 -- ✅ 提供具体位置 -- ✅ 切片被阻断 - ---- - -#### TC03: 边缘物体+螺旋抬升 - 降级验证 -**目的**: 验证螺旋抬升自动降级机制 - -**步骤**: -1. 创建大物体(190×190mm,对于200×200床) -2. 启用**螺旋抬升** -3. 切片 - -**预期结果**: -- ✅ 切片成功 -- ⚠️ 日志中出现降级警告: - ``` - Spiral lift arc exceeds build volume boundaries, - downgrading to lazy lift - ``` - 或 - ``` - Lazy lift slope exceeds build volume boundaries, - downgrading to normal lift - ``` - -**验证点**: -- ✅ 自动降级生效 -- ✅ 打印路径仍在床内 -- ✅ 不影响切片完成 - ---- - -### P1 - 应该测试 (High) - -#### TC04: Travel移动检测 -**目的**: 验证Travel移动边界检查 - -**步骤**: -1. 创建两个物体,分开放置在床的对角 -2. 确保它们之间的Travel路径会经过床边缘附近 -3. 切片 -4. 检查G-code预览 - -**预期结果**: -- ✅ 如果Travel在边界内:正常显示 -- ⚠️ 如果Travel超限:橙色警告 - ---- - -#### TC05: Skirt边界检查 -**目的**: 验证Skirt超出边界时记录警告 - -**步骤**: -1. 创建195×195mm物体(对于200×200床) -2. 设置Skirt距离为10mm -3. 切片 - -**预期结果**: -- ✅ 切片成功 -- ⚠️ 日志中记录Skirt超限警告 -- ✅ 不阻断切片 - ---- - -#### TC06: 支撑边界检查 -**目的**: 验证支撑超出边界时记录警告 - -**步骤**: -1. 创建需要大量支撑的模型 -2. 确保支撑可能延伸到床边缘 -3. 切片 - -**预期结果**: -- ✅ 切片成功 -- ⚠️ 如有超限,日志记录警告 -- ✅ 不阻断切片 - ---- - -### P2 - 可选测试 (Medium) - -#### TC07: G2/G3弧线命令验证 -**目的**: 验证Python工具能检测弧线超限 - -**步骤**: -1. 生成包含G2/G3命令的G-code -2. 使用Python工具分析: - ```bash - python analyze_gcode_bounds.py output.gcode --bed-size 200 200 250 - ``` - -**预期结果**: -- ✅ 能正确解析G2/G3 -- ✅ 能检测弧线超限 - ---- - -#### TC08: 极限边界打印 -**目的**: 验证紧贴边界的打印 - -**步骤**: -1. 创建199×199mm物体(对于200×200床) -2. 放置在床的角落 -3. 启用所有功能(螺旋抬升、Skirt、Brim、支撑) -4. 切片 - -**预期结果**: -- ✅ 切片成功(功能降级生效) -- ⚠️ 可能有多个降级警告 -- ✅ G-code仍在边界内 - ---- - -## 📊 性能影响评估 - -### 切片性能 - -| 阶段 | 性能影响 | 说明 | -|------|----------|------| -| 模型加载/处理 | < 0.1% | 无影响 | -| Skirt/Brim生成 | 1-2% | 验证开销 | -| 支撑生成 | 1-2% | 验证开销 | -| 擦料塔验证 | < 0.1% | 一次性检查 | -| G-code生成 | 0% | 无影响 | -| **总体** | **< 5%** | 用户无感知 | - -### 内存影响 - -- 边界违规记录:每个约100字节 -- 对于典型切片:< 1MB -- **影响**: 可忽略 - ---- - -## 🔙 回滚方案 - -### 快速回滚(如有问题) - -**方法1: Git Revert** -```bash -# 回滚到修改前的commit -git revert -git revert -# ... 回滚所有相关commit -``` - -**方法2: 手动修改关键文件** - -如果发现特定问题,可以临时禁用某些检查: - -1. **禁用Travel检查** - `GCodeViewer.cpp:2403-2450` - ```cpp - // 注释掉这段代码 - /* - if (m_contained_in_bed) { - bool all_moves_valid = build_volume.all_moves_inside(...); - ... - } - */ - ``` - -2. **降低擦料塔检查严格度** - `Print.cpp:1290-1327` - ```cpp - // 改为警告而非错误 - BOOST_LOG_TRIVIAL(warning) << "Wipe tower outside bounds"; - // 不要 return 错误 - ``` - -3. **禁用抬升降级** - `GCodeWriter.cpp:574-591, 633-649` - ```cpp - // 注释掉降级逻辑,保持原有类型 - ``` - ---- - -## 📝 发布说明模板 - -### 更新日志建议 - -``` -边界检测增强 (v2.x.0) -==================== - -✨ 新功能 -- 添加Travel移动边界验证,防止打印头超出范围 -- 添加擦料塔位置验证,避免设置错误 -- 添加Skirt/Brim/支撑边界检查 -- 添加螺旋/懒惰抬升自动降级机制 - -🔧 改进 -- 提升边界检测精度和覆盖率 -- 优化边缘区域的打印安全性 - -⚠️ 重要提示 -- 如果擦料塔位置超出打印床范围,切片将失败 -- 边缘区域的抬升方式可能自动调整(螺旋→懒惰→普通) -- 建议:将物体放置在距离边缘至少5mm的位置 - -🐛 修复 -- 修复Travel移动可能超出边界的问题 -- 修复擦料塔不验证位置的问题 -``` - ---- - -## 📋 发布前检查清单 - -### 代码审查 -- [x] 代码变更已审查 -- [x] 遵循项目编码规范 -- [x] 无内存泄漏风险 -- [x] 边界条件已处理 - -### 测试验证 -- [ ] TC01-TC06 测试用例全部通过 -- [ ] 回归测试通过 -- [ ] 性能测试通过(切片时间增加<5%) -- [ ] 边缘打印场景验证 - -### 文档 -- [x] 实施文档完整 -- [x] 风险评估完成 -- [ ] 更新日志已准备 -- [ ] 用户文档已更新(如需要) - -### 兼容性 -- [ ] Windows编译通过 -- [ ] macOS编译通过(如支持) -- [ ] Linux编译通过(如支持) -- [ ] 现有配置文件兼容 - ---- - -## 🎯 风险总结 - -### 优点 ✅ - -1. **安全性大幅提升** - - 防止打印头撞机 - - 防止超出边界的移动 - - 自动降级保护 - -2. **用户体验改善** - - 更清晰的错误信息 - - 自动处理临界情况 - - 无需手动干预 - -3. **代码质量提升** - - 统一的验证框架 - - 可扩展架构 - - 详细日志记录 - -### 潜在问题 ⚠️ - -1. **擦料塔位置错误** - - **影响**: 切片失败 - - **概率**: 中 - - **缓解**: 清晰错误信息 - -2. **边缘打印行为改变** - - **影响**: 抬升方式可能不同 - - **概率**: 低(仅边缘) - - **缓解**: 自动降级 - -3. **现有配置可能需要调整** - - **影响**: 可能显示新警告 - - **概率**: 低 - - **缓解**: 调整参数 - ---- - -## 📞 问题响应预案 - -### 如果用户报告问题 - -**问题1**: "之前能切片,现在失败了" -- **可能原因**: 擦料塔位置超出边界 -- **解决方案**: - ``` - 检查擦料塔位置设置(打印机设置 → 高级 → 擦料塔位置) - 确保在打印床范围内(考虑brim宽度) - ``` - -**问题2**: "显示toolpath_outside警告" -- **可能原因**: Travel移动超出边界 -- **解决方案**: - ``` - 检查物体是否过于靠近床边缘 - 检查擦料塔位置 - 尝试将物体向中心移动5-10mm - ``` - -**问题3**: "打印质量下降" -- **可能原因**: 抬升方式改变(边缘) -- **解决方案**: - ``` - 关闭"螺旋抬升"选项 - 或将物体远离边缘 - ``` - ---- - -## 📈 建议发布策略 - -### 渐进式发布(推荐) - -1. **Beta测试** (1-2周) - - 发布给内部测试人员 - - 收集反馈 - - 修复发现的问题 - -2. **RC发布** (1周) - - 发布给早期采用者 - - 监控反馈 - - 准备回滚方案 - -3. **正式发布** - - 包含详细更新日志 - - 提供迁移指南 - - 监控用户反馈 - -### 回滚触发条件 - -如果出现以下情况,考虑回滚: -- ❌ 大量用户报告切片失败 -- ❌ 发现严重打印质量问题 -- ❌ 性能下降超过10% - ---- - -## ✅ 最终建议 - -### 建议:**可以发布,但需要充分测试** - -**理由**: -1. ✅ 核心功能实现正确 -2. ✅ 有完善的降级机制 -3. ✅ 风险可控且可识别 -4. ✅ 有清晰的回滚方案 - -**前提条件**: -1. ⚠️ **必须**通过TC01-TC06测试 -2. ⚠️ **必须**准备更新日志和用户指南 -3. ⚠️ **建议**先进行Beta测试 - -**发布后监控**: -- 关注用户反馈 -- 监控错误报告 -- 准备快速补丁 - ---- - -**文档结束** - -**风险评估**: 🟡 中等风险 -**建议**: ✅ 建议发布(充分测试后) -**最后更新**: 2026-01-20 diff --git a/localization/i18n/Snapmaker_Orca.pot b/localization/i18n/Snapmaker_Orca.pot index e3ea9ca77d..019575d0c5 100644 --- a/localization/i18n/Snapmaker_Orca.pot +++ b/localization/i18n/Snapmaker_Orca.pot @@ -4331,72 +4331,6 @@ msgstr "" msgid "A G-code path goes beyond the plate boundaries." msgstr "" -msgid "G-code boundary violations detected:\n\n" -msgstr "" - -msgid "Travel Move" -msgstr "" - -msgid "Extrude Move" -msgstr "" - -msgid "Spiral Lift" -msgstr "" - -msgid "Lazy Lift" -msgstr "" - -msgid "Wipe Tower" -msgstr "" - -msgid "Skirt" -msgstr "" - -msgid "Arc Move" -msgstr "" - -msgid "violation(s)" -msgstr "" - -msgid "violations" -msgstr "" - -msgid "Total" -msgstr "" - -msgid "Details" -msgstr "" - -msgid "at Z" -msgstr "" - -msgid "%.2f mm out" -msgstr "" - -msgid "... and more" -msgstr "" - -msgid "beyond X minimum" -msgstr "" - -msgid "beyond X maximum" -msgstr "" - -msgid "beyond Y minimum" -msgstr "" - -msgid "beyond Y maximum" -msgstr "" - -msgid "above Z maximum" -msgstr "" - -msgid "beyond bed radius" -msgstr "" - -msgid "outside boundaries" -msgstr "" - msgid "Only the object being edited is visible." msgstr "" diff --git a/localization/i18n/zh_CN/Snapmaker_Orca_zh_CN.po b/localization/i18n/zh_CN/Snapmaker_Orca_zh_CN.po index e2946af806..307b50e17f 100644 --- a/localization/i18n/zh_CN/Snapmaker_Orca_zh_CN.po +++ b/localization/i18n/zh_CN/Snapmaker_Orca_zh_CN.po @@ -4257,63 +4257,6 @@ msgstr "检测出超出打印高度的G-code路径。" msgid "A G-code path goes beyond the plate boundaries." msgstr "检测超出热床边界的G-code路径。" -msgid "G-code boundary violations detected:\n\n" -msgstr "检测到G-code超出边界:\n\n" - -msgid "Travel Move" -msgstr "移动" - -msgid "Extrude Move" -msgstr "挤出移动" - -msgid "Spiral Lift" -msgstr "螺旋抬升" - -msgid "Lazy Lift" -msgstr "惰性抬升" - -msgid "Wipe Tower" -msgstr "擦料塔" - -msgid "Arc Move" -msgstr "圆弧移动" - -msgid "violation(s)" -msgstr "处违规" - -msgid "violations" -msgstr "违规" - -msgid "at Z" -msgstr "在Z" - -msgid "%.2f mm out" -msgstr "超出%.2f毫米" - -msgid "... and more" -msgstr "...以及更多" - -msgid "beyond X minimum" -msgstr "超出X最小值" - -msgid "beyond X maximum" -msgstr "超出X最大值" - -msgid "beyond Y minimum" -msgstr "超出Y最小值" - -msgid "beyond Y maximum" -msgstr "超出Y最大值" - -msgid "above Z maximum" -msgstr "高于Z最大值" - -msgid "beyond bed radius" -msgstr "超出热床半径" - -msgid "outside boundaries" -msgstr "超出边界" - msgid "Only the object being edited is visible." msgstr "只有正在编辑的对象是可见的。" diff --git a/src/libslic3r/BoundaryValidator.cpp b/src/libslic3r/BoundaryValidator.cpp deleted file mode 100644 index f8aa62fbe0..0000000000 --- a/src/libslic3r/BoundaryValidator.cpp +++ /dev/null @@ -1,215 +0,0 @@ -#include "BoundaryValidator.hpp" -#include "Geometry.hpp" -#include "libslic3r.h" -#include - -namespace Slic3r { - -// ============================================================================ -// BoundaryValidator static methods -// ============================================================================ - -std::string BoundaryValidator::violation_type_name(ViolationType type) -{ - switch (type) { - case ViolationType::Unknown: - return "Unknown"; - case ViolationType::TravelMove: - return "Travel Move"; - case ViolationType::ExtrudeMove: - return "Extrude Move"; - case ViolationType::SpiralLift: - return "Spiral Lift"; - case ViolationType::LazyLift: - return "Lazy Lift"; - case ViolationType::WipeTower: - return "Wipe Tower"; - case ViolationType::Skirt: - return "Skirt"; - case ViolationType::Brim: - return "Brim"; - case ViolationType::Support: - return "Support"; - case ViolationType::ArcMove: - return "Arc Move"; - default: - return "Unknown Violation"; - } -} - -// ============================================================================ -// BuildVolumeBoundaryValidator implementation -// ============================================================================ - -BuildVolumeBoundaryValidator::BuildVolumeBoundaryValidator( - const BuildVolume& build_volume, - double epsilon) - : m_build_volume(build_volume), m_epsilon(epsilon) -{ -} - -bool BuildVolumeBoundaryValidator::validate_point(const Vec3d& point) const -{ - // Validate Z height first (if printable_height is set) - if (m_build_volume.printable_height() > 0.0) { - if (point.z() > m_build_volume.printable_height() + m_epsilon) { - return false; - } - } - - // Validate XY position based on build volume type - return is_inside_2d(Vec2d(point.x(), point.y())); -} - -bool BuildVolumeBoundaryValidator::validate_line(const Vec3d& from, const Vec3d& to) const -{ - // For line validation, we sample multiple points along the line - // to ensure the entire segment is within boundaries - const int num_samples = 10; - - for (int i = 0; i <= num_samples; ++i) { - double t = static_cast(i) / num_samples; - Vec3d sample_point = from + t * (to - from); - - if (!validate_point(sample_point)) { - return false; - } - } - - return true; -} - -bool BuildVolumeBoundaryValidator::validate_arc( - const Vec3d& center, - double radius, - double start_angle, - double end_angle, - double z_height) const -{ - // Sample points along the arc and validate each - std::vector arc_points = sample_arc_points( - center, radius, start_angle, end_angle, z_height - ); - - for (const Vec3d& point : arc_points) { - if (!validate_point(point)) { - return false; - } - } - - return true; -} - -bool BuildVolumeBoundaryValidator::validate_polygon(const Polygon& poly, double z_height) const -{ - // Check if Z height is valid - if (m_build_volume.printable_height() > 0.0) { - if (z_height > m_build_volume.printable_height() + m_epsilon) { - return false; - } - } - - // Check all polygon vertices - for (const Point& pt : poly.points) { - Vec2d unscaled_pt = unscale(pt); - if (!is_inside_2d(unscaled_pt)) { - return false; - } - } - - return true; -} - -std::vector BuildVolumeBoundaryValidator::sample_arc_points( - const Vec3d& center, - double radius, - double start_angle, - double end_angle, - double z_height, - int num_samples) const -{ - std::vector points; - points.reserve(num_samples); - - // Handle angle wrapping (e.g., from 350° to 10° should go through 360°/0°) - double angle_range = end_angle - start_angle; - - // Normalize to handle wrapping - if (angle_range < 0) { - angle_range += 2 * PI; - } - - for (int i = 0; i < num_samples; ++i) { - double t = static_cast(i) / (num_samples - 1); - double angle = start_angle + t * angle_range; - - double x = center.x() + radius * std::cos(angle); - double y = center.y() + radius * std::sin(angle); - - points.emplace_back(x, y, z_height); - } - - return points; -} - -bool BuildVolumeBoundaryValidator::is_inside_2d(const Vec2d& point) const -{ - const BuildVolume_Type type = m_build_volume.type(); - - switch (type) { - case BuildVolume_Type::Rectangle: - { - // Get the bounding box of the build volume - const BoundingBoxf& bbox = m_build_volume.bounding_volume2d(); - BoundingBoxf inflated_bbox = bbox; - inflated_bbox.min -= Vec2d(m_epsilon, m_epsilon); - inflated_bbox.max += Vec2d(m_epsilon, m_epsilon); - - return inflated_bbox.contains(point); - } - - case BuildVolume_Type::Circle: - { - // Get circle parameters - circle.center is already in scaled coordinates - const Geometry::Circled& circle = m_build_volume.circle(); - const Vec2d center_unscaled(unscale(circle.center.x()), - unscale(circle.center.y())); - const double radius = unscale(circle.radius) + m_epsilon; - - // Check distance from center - double dist_sq = (point - center_unscaled).squaredNorm(); - return dist_sq <= radius * radius; - } - - case BuildVolume_Type::Convex: - case BuildVolume_Type::Custom: - { - // For convex/custom volumes, use point-in-polygon test - // Get the convex hull decomposition - this returns pair - const auto& decomp = m_build_volume.top_bottom_convex_hull_decomposition_bed(); - const std::vector& top_hull = decomp.first; - - if (top_hull.empty()) { - return false; - } - - // Check if point is inside the top convex hull - Point scaled_point = scaled(point); - - // Build polygon from Vec2d points - Polygon hull_poly; - for (const Vec2d& pt : top_hull) { - hull_poly.points.push_back(scaled(pt)); - } - - return hull_poly.contains(scaled_point); - } - - case BuildVolume_Type::Invalid: - default: - // If build volume type is invalid, allow everything (fail-safe) - return true; - } -} - -} // namespace Slic3r diff --git a/src/libslic3r/BoundaryValidator.hpp b/src/libslic3r/BoundaryValidator.hpp deleted file mode 100644 index 0c80f2faf7..0000000000 --- a/src/libslic3r/BoundaryValidator.hpp +++ /dev/null @@ -1,168 +0,0 @@ -#ifndef slic3r_BoundaryValidator_hpp_ -#define slic3r_BoundaryValidator_hpp_ - -#include "Point.hpp" -#include "Polygon.hpp" -#include "BuildVolume.hpp" -#include -#include - -namespace Slic3r { - -/** - * @brief Abstract interface for validating geometric elements against print boundaries - * - * This class provides a unified interface for boundary validation across different - * parts of the slicing pipeline. Implementations can validate points, lines, arcs, - * and polygons against the build volume. - */ -class BoundaryValidator { -public: - /** - * @brief Types of boundary violations that can occur - */ - enum class ViolationType { - Unknown = 0, - TravelMove, // Travel move exceeds boundaries - ExtrudeMove, // Extrude move exceeds boundaries - SpiralLift, // Spiral lift arc exceeds boundaries - LazyLift, // Lazy lift slope exceeds boundaries - WipeTower, // Wipe tower position exceeds boundaries - Skirt, // Skirt exceeds boundaries - Brim, // Brim exceeds boundaries - Support, // Support material exceeds boundaries - ArcMove, // G2/G3 arc move exceeds boundaries - Count // Sentinel value - }; - - /** - * @brief Direction of boundary violation - */ - enum class BoundaryDirection { - Unknown = 0, - X_Min, // Beyond X minimum boundary - X_Max, // Beyond X maximum boundary - Y_Min, // Beyond Y minimum boundary - Y_Max, // Beyond Y maximum boundary - Z_Max, // Above Z maximum boundary - Radius, // Beyond circular bed radius - Count // Sentinel value - }; - - /** - * @brief Describes a single boundary violation - */ - struct BoundaryViolation { - ViolationType type; // Type of violation - BoundaryDirection direction; // Direction of violation - std::string description; // Human-readable description - Vec3d position; // Position where violation occurs (unscaled) - double distance_out; // How far outside boundaries (unscaled) - double layer_z; // Z height of the layer (unscaled) - std::string object_name; // Name of related object (if applicable) - - BoundaryViolation(ViolationType t, const std::string& desc, - const Vec3d& pos, double z, const std::string& obj = "", - BoundaryDirection dir = BoundaryDirection::Unknown, double dist = 0.0) - : type(t), direction(dir), description(desc), position(pos), distance_out(dist), layer_z(z), object_name(obj) {} - }; - - using BoundaryViolations = std::vector; - - virtual ~BoundaryValidator() = default; - - /** - * @brief Validate a single point against boundaries - * @param point Point to validate (unscaled coordinates) - * @return true if point is within boundaries, false otherwise - */ - virtual bool validate_point(const Vec3d& point) const = 0; - - /** - * @brief Validate a line segment against boundaries - * @param from Start point (unscaled coordinates) - * @param to End point (unscaled coordinates) - * @return true if entire line is within boundaries, false otherwise - */ - virtual bool validate_line(const Vec3d& from, const Vec3d& to) const = 0; - - /** - * @brief Validate an arc path against boundaries - * @param center Arc center point (unscaled coordinates) - * @param radius Arc radius (unscaled) - * @param start_angle Start angle in radians - * @param end_angle End angle in radians - * @param z_height Z height of the arc (unscaled) - * @return true if entire arc is within boundaries, false otherwise - */ - virtual bool validate_arc(const Vec3d& center, double radius, - double start_angle, double end_angle, - double z_height) const = 0; - - /** - * @brief Validate a polygon against boundaries - * @param poly Polygon to validate (scaled coordinates) - * @param z_height Z height of the polygon (unscaled) - * @return true if entire polygon is within boundaries, false otherwise - */ - virtual bool validate_polygon(const Polygon& poly, double z_height = 0.0) const = 0; - - /** - * @brief Get human-readable name for violation type - */ - static std::string violation_type_name(ViolationType type); -}; - -/** - * @brief Concrete implementation of BoundaryValidator based on BuildVolume - * - * This validator uses the BuildVolume class to perform boundary checks. - * It supports all build volume types (Rectangle, Circle, Convex, Custom). - */ -class BuildVolumeBoundaryValidator : public BoundaryValidator { -public: - /** - * @brief Construct validator from BuildVolume - * @param build_volume Reference to the build volume - * @param epsilon Tolerance for boundary checks (default: BedEpsilon) - */ - explicit BuildVolumeBoundaryValidator(const BuildVolume& build_volume, - double epsilon = BuildVolume::BedEpsilon); - - bool validate_point(const Vec3d& point) const override; - bool validate_line(const Vec3d& from, const Vec3d& to) const override; - bool validate_arc(const Vec3d& center, double radius, - double start_angle, double end_angle, - double z_height) const override; - bool validate_polygon(const Polygon& poly, double z_height = 0.0) const override; - -private: - const BuildVolume& m_build_volume; - double m_epsilon; - - /** - * @brief Sample points along an arc for validation - * @param center Arc center (unscaled) - * @param radius Arc radius (unscaled) - * @param start_angle Start angle in radians - * @param end_angle End angle in radians - * @param z_height Z height (unscaled) - * @param num_samples Number of sample points (default: 16) - * @return Vector of sampled points - */ - std::vector sample_arc_points(const Vec3d& center, double radius, - double start_angle, double end_angle, - double z_height, - int num_samples = 16) const; - - /** - * @brief Check if a 2D point is inside the build volume - * @param point 2D point (unscaled) - * @return true if inside, false otherwise - */ - bool is_inside_2d(const Vec2d& point) const; -}; - -} // namespace Slic3r - -#endif // slic3r_BoundaryValidator_hpp_ diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 65f52dcbc6..e865efc781 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -8,9 +8,6 @@ #include "libslic3r.h" #include "PrintConfig.hpp" #include "Model.hpp" -#include "BoundaryValidator.hpp" -#include "BuildVolume.hpp" -#include "GCode/GCodeProcessor.hpp" #include #include #include @@ -990,7 +987,7 @@ static ExPolygons outer_inner_brim_area(const Print& print, polygons_reverse(ex_poly_holes_reversed); if (has_outer_brim) { - // Snapmaker: inner and outer boundary are offset from the same polygon incase of round off error. + // BBS: inner and outer boundary are offset from the same polygon incase of round off error. auto innerExpoly = offset_ex(ex_poly.contour, brim_offset, jtRound, SCALED_RESOLUTION); ExPolygons outerExpoly; if (use_brim_ears) { @@ -1693,8 +1690,7 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_ std::map& brimMap, std::map& supportBrimMap, std::vector> &objPrintVec, - std::vector& printExtruders, - Print* print_ptr) + std::vector& printExtruders) { double brim_width_max = 0; @@ -1742,68 +1738,13 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_ for (size_t iia = 0; iia < islands_area.size(); ++iia) islands_area[iia].translate(plate_shift); - // Snapmaker: Create BuildVolume and BoundaryValidator for brim boundary checking - BuildVolume build_volume(print.config().printable_area.values, print.config().printable_height); - BuildVolumeBoundaryValidator validator(build_volume); - double first_layer_height = print.skirt_first_layer_height(); - for (auto iter = brimAreaMap.begin(); iter != brimAreaMap.end(); ++iter) { if (!iter->second.empty()) { - // Snapmaker: Validate brim area against build volume boundaries - for (const ExPolygon& expoly : iter->second) { - if (!validator.validate_polygon(expoly.contour, first_layer_height)) { - // Record boundary violation - if (print_ptr) { - BoundingBox bbox = get_extents(expoly.contour); - Vec3d violation_pos( - unscale(bbox.center().x()), - unscale(bbox.center().y()), - first_layer_height - ); - PrintObject* obj = const_cast(print.get_object(iter->first)); - std::string obj_name = obj ? obj->model_object()->name : "Unknown"; - ConflictResult violation = ConflictResult::create_boundary_violation( - static_cast(BoundaryValidator::ViolationType::Brim), - violation_pos, - first_layer_height, - obj_name - ); - print_ptr->add_boundary_violation(violation); - BOOST_LOG_TRIVIAL(warning) << "Brim for object " << obj_name - << " exceeds build volume boundaries at z=" << first_layer_height << " mm"; - } - } - } brimMap.insert(std::make_pair(iter->first, makeBrimInfill(iter->second, print, islands_area))); }; } for (auto iter = supportBrimAreaMap.begin(); iter != supportBrimAreaMap.end(); ++iter) { if (!iter->second.empty()) { - // Snapmaker: Validate support brim area against build volume boundaries - for (const ExPolygon& expoly : iter->second) { - if (!validator.validate_polygon(expoly.contour, first_layer_height)) { - // Record boundary violation - if (print_ptr) { - BoundingBox bbox = get_extents(expoly.contour); - Vec3d violation_pos( - unscale(bbox.center().x()), - unscale(bbox.center().y()), - first_layer_height - ); - PrintObject* obj = const_cast(print.get_object(iter->first)); - std::string obj_name = obj ? obj->model_object()->name : "Unknown"; - ConflictResult violation = ConflictResult::create_boundary_violation( - static_cast(BoundaryValidator::ViolationType::Brim), - violation_pos, - first_layer_height, - obj_name + " (support brim)" - ); - print_ptr->add_boundary_violation(violation); - BOOST_LOG_TRIVIAL(warning) << "Support brim for object " << obj_name - << " exceeds build volume boundaries at z=" << first_layer_height << " mm"; - } - } - } supportBrimMap.insert(std::make_pair(iter->first, makeBrimInfill(iter->second, print, islands_area))); }; } diff --git a/src/libslic3r/Brim.hpp b/src/libslic3r/Brim.hpp index 46bb0d0763..4ae592a26b 100644 --- a/src/libslic3r/Brim.hpp +++ b/src/libslic3r/Brim.hpp @@ -15,13 +15,11 @@ class ObjectID; // Produce brim lines around those objects, that have the brim enabled. // Collect islands_area to be merged into the final 1st layer convex hull. -// If print_ptr is provided (non-const), boundary violations will be reported. void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_area, std::map& brimMap, std::map& supportBrimMap, std::vector>& objPrintVec, - std::vector& printExtruders, - Print* print_ptr = nullptr); + std::vector& printExtruders); // BBS: automatically make brim ExtrusionEntityCollection make_brim_auto(const Print &print, PrintTryCancel try_cancel, Polygons &islands_area); diff --git a/src/libslic3r/BuildVolume.cpp b/src/libslic3r/BuildVolume.cpp index 301bc39194..147a556aa3 100644 --- a/src/libslic3r/BuildVolume.cpp +++ b/src/libslic3r/BuildVolume.cpp @@ -347,7 +347,7 @@ bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const Boun const Vec2f c = unscaled(m_circle.center); const float r = unscaled(m_circle.radius) + epsilon; const float r2 = sqr(r); - return m_max_print_height == 0.0 ? + return m_max_print_height == 0.0 ? std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, c, r2](const GCodeProcessorResult::MoveVertex &move) { return ! move_valid(move) || (to_2d(move.position) - c).squaredNorm() <= r2; }) : std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, c, r2, z = m_max_print_height + epsilon](const GCodeProcessorResult::MoveVertex& move) @@ -357,7 +357,7 @@ bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const Boun //FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently. case BuildVolume_Type::Custom: return m_max_print_height == 0.0 ? - std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this](const GCodeProcessorResult::MoveVertex &move) + std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this](const GCodeProcessorResult::MoveVertex &move) { return ! move_valid(move) || Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(move.position).cast()); }) : std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this, z = m_max_print_height + epsilon](const GCodeProcessorResult::MoveVertex &move) { return ! move_valid(move) || (Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(move.position).cast()) && move.position.z() <= z); }); diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 180594f6da..da6a6b42c1 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -75,8 +75,6 @@ set(lisbslic3r_sources BlacklistedLibraryCheck.hpp BoundingBox.cpp BoundingBox.hpp - BoundaryValidator.cpp - BoundaryValidator.hpp BridgeDetector.cpp BridgeDetector.hpp Brim.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 354c5229f1..b43e52db9e 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -21,8 +21,6 @@ #include "libslic3r/format.hpp" #include "Time.hpp" #include "GCode/ExtrusionProcessor.hpp" -#include "BoundaryValidator.hpp" -#include "BuildVolume.hpp" #include #include #include @@ -1892,11 +1890,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream& file, ThumbnailsGenerato m_writer.set_is_bbl_machine(is_bbl_printers); - // Snapmaker: Initialize boundary validator for arc path validation - BuildVolume build_volume(print.config().printable_area.values, print.config().printable_height); - BuildVolumeBoundaryValidator validator(build_volume); - m_writer.set_boundary_validator(&validator, &print); - // How many times will be change_layer() called? // change_layer() in turn increments the progress bar status. m_layer_count = 0; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 639b464bb0..f770c8579f 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -574,7 +574,6 @@ void GCodeProcessorResult::reset() { custom_gcode_per_print_z = std::vector(); spiral_vase_layers = std::vector>>(); time = 0; - boundary_violations.clear(); //BBS: add mutex for protection of gcode result unlock(); @@ -608,7 +607,6 @@ void GCodeProcessorResult::reset() { spiral_vase_layers = std::vector>>(); bed_match_result = BedMatchResult(true); warnings.clear(); - boundary_violations.clear(); //BBS: add mutex for protection of gcode result unlock(); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 7e5d2c35b4..64dc41e567 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -6,7 +6,6 @@ #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/CustomGCode.hpp" -#include "libslic3r/BoundaryValidator.hpp" #include #include @@ -105,66 +104,18 @@ class Print; } }; - // Forward declaration for BoundaryValidator - class BoundaryValidator; - struct ConflictResult { - // ===== Existing fields for object collision ===== std::string _objName1; std::string _objName2; double _height; const void *_obj1; // nullptr means wipe tower const void *_obj2; int layer = -1; - - // ===== New fields for boundary violations ===== - enum class ConflictType { - ObjectCollision, // Original: collision between objects - BoundaryViolation // New: path exceeds build volume boundaries - }; - - ConflictType conflict_type = ConflictType::ObjectCollision; - - // Only valid when conflict_type == BoundaryViolation - int violation_type_int = -1; // Stores BoundaryValidator::ViolationType as int - Vec3d violation_position{Vec3d::Zero()}; // Position where violation occurs (unscaled) - - // ===== Constructors ===== ConflictResult(const std::string &objName1, const std::string &objName2, double height, const void *obj1, const void *obj2) - : _objName1(objName1), _objName2(objName2), _height(height), _obj1(obj1), _obj2(obj2), - conflict_type(ConflictType::ObjectCollision) + : _objName1(objName1), _objName2(objName2), _height(height), _obj1(obj1), _obj2(obj2) {} - ConflictResult() = default; - - // New: Static factory method for boundary violations - // Note: violation_type should be cast from BoundaryValidator::ViolationType - static ConflictResult create_boundary_violation( - int violation_type, - const Vec3d& pos, - double height, - const std::string& obj_name = "" - ) { - ConflictResult result; - result.conflict_type = ConflictType::BoundaryViolation; - result.violation_type_int = violation_type; - result.violation_position = pos; - result._height = height; - result._objName1 = obj_name; - result.layer = -1; // Will be computed later if needed - return result; - } - - // Helper method to check if this is a boundary violation - bool is_boundary_violation() const { - return conflict_type == ConflictType::BoundaryViolation; - } - - // Helper method to check if this is an object collision - bool is_object_collision() const { - return conflict_type == ConflictType::ObjectCollision; - } }; struct BedMatchResult @@ -240,18 +191,6 @@ class Print; std::vector params; // extra msg info }; - // Snapmaker: Detailed boundary violation information for better user feedback - // Uses BoundaryValidator enums to avoid duplication - struct BoundaryViolationInfo { - BoundaryValidator::ViolationType violation_type{BoundaryValidator::ViolationType::Unknown}; - BoundaryValidator::BoundaryDirection direction{BoundaryValidator::BoundaryDirection::Unknown}; - Vec3d position{Vec3d::Zero()}; // Position where violation occurs (mm) - double distance_out{0.0}; // How far outside (mm) - std::string component_name; // e.g., "Skirt", "Brim", "Support", "Wipe Tower" - int layer_num{-1}; // Layer number (if applicable) - float print_z{-1.0f}; // Z height at violation (mm) - }; - std::string filename; unsigned int id; std::vector moves; @@ -283,8 +222,6 @@ class Print; std::vector>> spiral_vase_layers; //BBS std::vector warnings; - // Snapmaker: Detailed boundary violation information - std::vector boundary_violations; int nozzle_hrc; NozzleType nozzle_type; BedType bed_type = BedType::btCount; @@ -318,7 +255,6 @@ class Print; custom_gcode_per_print_z = other.custom_gcode_per_print_z; spiral_vase_layers = other.spiral_vase_layers; warnings = other.warnings; - boundary_violations = other.boundary_violations; bed_type = other.bed_type; bed_match_result = other.bed_match_result; #if ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index b4dd89a9db..ba36c2b6d4 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -1,15 +1,11 @@ #include "GCodeWriter.hpp" #include "CustomGCode.hpp" -#include "BoundaryValidator.hpp" -#include "BuildVolume.hpp" -#include "Print.hpp" -#include "GCode/GCodeProcessor.hpp" #include #include #include #include #include -#include +#include #ifdef __APPLE__ #include @@ -549,116 +545,29 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co if (delta(2) > 0 && delta_no_z.norm() != 0.0f) { //BBS: SpiralLift if (m_to_lift_type == LiftType::SpiralLift && this->is_current_position_clear()) { - // Calculate the radius of the spiral arc + //BBS: todo: check the arc move all in bed area, if not, then use lazy lift double radius = delta(2) / (2 * PI * atan(this->extruder()->travel_slope())); - - // Calculate arc center and angles for precise boundary validation Vec2d ij_offset = radius * delta_no_z.normalized(); ij_offset = { -ij_offset(1), ij_offset(0) }; - - // Arc center is source + ij_offset (in unscaled coordinates) - Vec3d arc_center = source + Vec3d(ij_offset(0), ij_offset(1), 0); - - // Calculate start and end angles - // ij_offset is perpendicular to delta_no_z, so the arc starts from -ij_offset direction - double start_angle = std::atan2(-ij_offset(1), -ij_offset(0)); - double end_angle = start_angle + 2 * PI; // Full circle - - // Snapmaker: Use BoundaryValidator for precise arc validation - bool arc_valid = true; - if (m_boundary_validator) { - arc_valid = m_boundary_validator->validate_arc( - arc_center, radius, start_angle, end_angle, source.z() - ); - - if (!arc_valid) { - // Record boundary violation - if (m_print_ptr) { - Vec3d violation_pos = arc_center + Vec3d(radius, 0, source.z()); - ConflictResult violation = ConflictResult::create_boundary_violation( - static_cast(BoundaryValidator::ViolationType::SpiralLift), - violation_pos, - source.z(), - "Spiral Lift" - ); - m_print_ptr->add_boundary_violation(violation); - } - BOOST_LOG_TRIVIAL(warning) << "Spiral lift arc exceeds build volume boundaries, " - << "downgrading to lazy lift. Center: (" << arc_center.x() << ", " << arc_center.y() - << "), Radius: " << radius << " mm"; - // Fall through to LazyLift check below - m_to_lift_type = LiftType::LazyLift; - } - } else { - // Fallback: Simple radius check if validator not available - constexpr double MAX_SAFE_SPIRAL_RADIUS = 50.0; // mm - if (radius > MAX_SAFE_SPIRAL_RADIUS) { - BOOST_LOG_TRIVIAL(warning) << "Spiral lift radius (" << radius - << " mm) exceeds safe limit (" << MAX_SAFE_SPIRAL_RADIUS - << " mm), downgrading to lazy lift to prevent boundary violations"; - m_to_lift_type = LiftType::LazyLift; - arc_valid = false; - } - } - - if (arc_valid) { - slop_move = this->_spiral_travel_to_z(target(2), ij_offset, "spiral lift Z"); - } + slop_move = this->_spiral_travel_to_z(target(2), ij_offset, "spiral lift Z"); } //BBS: LazyLift - if (m_to_lift_type == LiftType::LazyLift && - this->is_current_position_clear() && + else if (m_to_lift_type == LiftType::LazyLift && + this->is_current_position_clear() && atan2(delta(2), delta_no_z.norm()) < this->extruder()->travel_slope()) { - // Calculate the slope top point + //BBS: check whether we can make a travel like + // _____ + // / to make the z list early to avoid to hit some warping place when travel is long. Vec2d temp = delta_no_z.normalized() * delta(2) / tan(this->extruder()->travel_slope()); Vec3d slope_top_point = Vec3d(temp(0), temp(1), delta(2)) + source; - - // Snapmaker: Use BoundaryValidator for precise line validation - bool slope_valid = true; - if (m_boundary_validator) { - // Validate the entire slope line from source to slope_top_point - slope_valid = m_boundary_validator->validate_line(source, slope_top_point); - - if (!slope_valid) { - // Record boundary violation - if (m_print_ptr) { - ConflictResult violation = ConflictResult::create_boundary_violation( - static_cast(BoundaryValidator::ViolationType::LazyLift), - slope_top_point, - source.z(), - "Lazy Lift" - ); - m_print_ptr->add_boundary_violation(violation); - } - BOOST_LOG_TRIVIAL(warning) << "Lazy lift slope exceeds build volume boundaries, " - << "downgrading to normal lift. Slope point: (" << slope_top_point.x() - << ", " << slope_top_point.y() << ", " << slope_top_point.z() << ")"; - // Fall through to NormalLift - m_to_lift_type = LiftType::NormalLift; - } - } else { - // Fallback: Simple distance check if validator not available - constexpr double MAX_SAFE_SLOPE_DISTANCE = 100.0; // mm - double slope_distance = temp.norm(); - if (slope_distance > MAX_SAFE_SLOPE_DISTANCE) { - BOOST_LOG_TRIVIAL(warning) << "Lazy lift slope distance (" << slope_distance - << " mm) exceeds safe limit (" << MAX_SAFE_SLOPE_DISTANCE - << " mm), downgrading to normal lift to prevent boundary violations"; - m_to_lift_type = LiftType::NormalLift; - slope_valid = false; - } - } - - if (slope_valid) { - GCodeG1Formatter w0; - w0.emit_xyz(slope_top_point); - w0.emit_f(travel_speed * 60.0); - //BBS - w0.emit_comment(GCodeWriter::full_gcode_comment, comment); - slop_move = w0.string(); - } + GCodeG1Formatter w0; + w0.emit_xyz(slope_top_point); + w0.emit_f(travel_speed * 60.0); + //BBS + w0.emit_comment(GCodeWriter::full_gcode_comment, comment); + slop_move = w0.string(); } - if (m_to_lift_type == LiftType::NormalLift) { + else if (m_to_lift_type == LiftType::NormalLift) { slop_move = _travel_to_z(target.z(), "normal lift Z"); } } @@ -825,59 +734,6 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: //center_offset is I and J axis std::string GCodeWriter::extrude_arc_to_xy(const Vec2d& point, const Vec2d& center_offset, double dE, const bool is_ccw, const std::string& comment, bool force_no_extrusion) { - // Snapmaker: Validate arc path against build volume boundaries - if (m_boundary_validator) { - // Calculate arc center (center_offset is relative to start point) - Vec2d start_point = { m_pos(0) - m_x_offset, m_pos(1) - m_y_offset }; - Vec3d arc_center = Vec3d(start_point(0) + center_offset(0), start_point(1) + center_offset(1), m_pos(2)); - - // Calculate radius from center offset - double radius = std::sqrt(center_offset(0) * center_offset(0) + center_offset(1) * center_offset(1)); - - // Calculate start and end angles - Vec2d start_vec = start_point - Vec2d(arc_center.x(), arc_center.y()); - Vec2d end_vec = Vec2d(point(0) - m_x_offset, point(1) - m_y_offset) - Vec2d(arc_center.x(), arc_center.y()); - - double start_angle = std::atan2(start_vec(1), start_vec(0)); - double end_angle = std::atan2(end_vec(1), end_vec(0)); - - // Handle CCW vs CW and angle wrapping - if (is_ccw) { - // For CCW, ensure end_angle > start_angle (wrapping if needed) - if (end_angle < start_angle) { - end_angle += 2 * PI; - } - } else { - // For CW, ensure end_angle < start_angle (wrapping if needed) - if (end_angle > start_angle) { - end_angle -= 2 * PI; - } - } - - // Validate the arc - bool arc_valid = m_boundary_validator->validate_arc( - arc_center, radius, start_angle, end_angle, m_pos(2) - ); - - if (!arc_valid) { - // Record boundary violation - if (m_print_ptr) { - Vec3d violation_pos = arc_center + Vec3d(radius, 0, m_pos(2)); - ConflictResult violation = ConflictResult::create_boundary_violation( - static_cast(BoundaryValidator::ViolationType::ArcMove), - violation_pos, - m_pos(2), - "Arc Extrusion" - ); - m_print_ptr->add_boundary_violation(violation); - } - BOOST_LOG_TRIVIAL(warning) << "Arc extrusion path exceeds build volume boundaries. " - << "Center: (" << arc_center.x() << ", " << arc_center.y() - << "), Radius: " << radius << " mm, Z: " << m_pos(2) << " mm"; - // Continue anyway (don't fail, just warn) - } - } - m_pos(0) = point(0); m_pos(1) = point(1); if (!force_no_extrusion) diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index 3388b340f0..103bfb27c2 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -11,10 +11,6 @@ namespace Slic3r { -// Forward declarations -class BoundaryValidator; -class Print; - class GCodeWriter { public: GCodeConfig config; @@ -123,12 +119,6 @@ public: void set_is_first_layer(bool bval) { m_is_first_layer = bval; } GCodeFlavor get_gcode_flavor() const { return config.gcode_flavor; } - // Snapmaker: Set boundary validator for arc path validation - void set_boundary_validator(const BoundaryValidator* validator, Print* print_ptr = nullptr) { - m_boundary_validator = validator; - m_print_ptr = print_ptr; - } - // Returns whether this flavor supports separate print and travel acceleration. static bool supports_separate_travel_acceleration(GCodeFlavor flavor); private: @@ -180,10 +170,6 @@ public: double m_current_speed; bool m_is_first_layer = true; - // Snapmaker: Boundary validator for arc path validation - const BoundaryValidator* m_boundary_validator = nullptr; - Print* m_print_ptr = nullptr; - enum class Acceleration { Travel, Print diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 4595037b33..2011a58636 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -36,7 +36,6 @@ #include "nlohmann/json.hpp" #include "GCode/ConflictChecker.hpp" -#include "BoundaryValidator.hpp" #include @@ -1288,45 +1287,6 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* } } } - - // Snapmaker: Critical fix - Validate wipe tower position is within build volume boundaries - // This addresses vulnerability #3: Wipe tower position was not validated against bed boundaries - { - const size_t plate_index = this->get_plate_index(); - const Vec3d plate_origin = this->get_plate_origin(); - const float x = m_config.wipe_tower_x.get_at(plate_index) + plate_origin(0); - const float y = m_config.wipe_tower_y.get_at(plate_index) + plate_origin(1); - const float width = m_config.prime_tower_width.value; - const float brim_width = m_config.prime_tower_brim_width.value; - const float depth = this->wipe_tower_data(extruders.size()).depth; - - // Check all four corners of wipe tower (including brim) - // Create a simple bounding box from printable_area config - BoundingBoxf bed_bbox; - for (const Vec2d& pt : m_config.printable_area.values) { - bed_bbox.merge(pt); - } - - bool tower_outside = false; - // Check all corners - if (x - brim_width < bed_bbox.min.x() || x + width + brim_width > bed_bbox.max.x() || - y - brim_width < bed_bbox.min.y() || y + depth + brim_width > bed_bbox.max.y()) { - tower_outside = true; - } - - if (tower_outside) { - const float total_width = width + 2 * brim_width; - const float total_depth = depth + 2 * brim_width; - return StringObjectException{ - Slic3r::format(_u8L("The prime tower at position (%.2f, %.2f) with dimensions %.2f x %.2f mm " - "(including %.2f mm brim) exceeds the bed boundaries. " - "Please adjust the prime tower position in the configuration."), - x, y, total_width, total_depth, brim_width), - nullptr, - "wipe_tower_x" - }; - } - } } { @@ -2188,7 +2148,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) if (this->has_brim()) { Polygons islands_area; make_brim(*this, this->make_try_cancel(), islands_area, m_brimMap, - m_supportBrimMap, objPrintVec, printExtruders, this); + m_supportBrimMap, objPrintVec, printExtruders); for (Polygon& poly_ex : islands_area) poly_ex.douglas_peucker(SCALED_RESOLUTION); for (Polygon &poly : union_(this->first_layer_islands(), islands_area)) @@ -2287,37 +2247,6 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor //BBS result->conflict_result = m_conflict_result; - - // Snapmaker: Copy boundary violations from Print to GCodeProcessorResult - // This allows detailed violation information to be displayed in the GUI - if (!m_boundary_violations.empty()) { - result->boundary_violations.clear(); - result->boundary_violations.reserve(m_boundary_violations.size()); - - for (const auto& conflict : m_boundary_violations) { - if (conflict.is_boundary_violation()) { - GCodeProcessorResult::BoundaryViolationInfo info; - - // Directly copy the violation type (enums are now unified) - info.violation_type = static_cast(conflict.violation_type_int); - - // Copy position and height - info.position = conflict.violation_position; - info.print_z = static_cast(conflict._height); - info.layer_num = conflict.layer; - - // Direction is not directly available in ConflictResult, leave as Unknown - info.direction = BoundaryValidator::BoundaryDirection::Unknown; - info.distance_out = 0.0; - - result->boundary_violations.push_back(info); - } - } - - BOOST_LOG_TRIVIAL(info) << "Copied " << result->boundary_violations.size() - << " boundary violations from Print to GCodeProcessorResult"; - } - return path.c_str(); } @@ -2414,11 +2343,6 @@ void Print::_make_skirt() // Draw outlines from outside to inside. // Loop while we have less skirts than required or any extruder hasn't reached the min length if any. std::vector extruded_length(extruders.size(), 0.); - - // Create BuildVolume and BoundaryValidator for skirt boundary checking - BuildVolume build_volume(m_config.printable_area.values, m_config.printable_height); - BuildVolumeBoundaryValidator validator(build_volume); - if (m_config.skirt_type == stCombined) { for (size_t i = m_config.skirt_loops, extruder_idx = 0; i > 0; -- i) { this->throw_if_canceled(); @@ -2434,28 +2358,6 @@ void Print::_make_skirt() break; loop = loops.front(); } - - // Snapmaker: Validate skirt loop against build volume boundaries - if (!validator.validate_polygon(loop, initial_layer_print_height)) { - // Record boundary violation - BoundingBox loop_bbox = get_extents(loop); - Vec3d violation_pos( - unscale(loop_bbox.center().x()), - unscale(loop_bbox.center().y()), - initial_layer_print_height - ); - ConflictResult violation = ConflictResult::create_boundary_violation( - static_cast(BoundaryValidator::ViolationType::Skirt), - violation_pos, - initial_layer_print_height, - "Skirt" - ); - this->add_boundary_violation(violation); - BOOST_LOG_TRIVIAL(warning) << "Skirt loop exceeds build volume boundaries at z=" - << initial_layer_print_height << " mm"; - // Continue with remaining loops but record the violation - } - // Extrude the skirt loop. ExtrusionLoop eloop(elrSkirt); eloop.paths.emplace_back(ExtrusionPath( @@ -2514,26 +2416,6 @@ void Print::_make_skirt() loop = loops.front(); } - // Snapmaker: Validate per-object skirt loop against build volume boundaries - if (!validator.validate_polygon(loop, initial_layer_print_height)) { - // Record boundary violation - BoundingBox loop_bbox = get_extents(loop); - Vec3d violation_pos( - unscale(loop_bbox.center().x()), - unscale(loop_bbox.center().y()), - initial_layer_print_height - ); - ConflictResult violation = ConflictResult::create_boundary_violation( - static_cast(BoundaryValidator::ViolationType::Skirt), - violation_pos, - initial_layer_print_height, - object->model_object()->name - ); - this->add_boundary_violation(violation); - BOOST_LOG_TRIVIAL(warning) << "Per-object skirt loop for " << object->model_object()->name - << " exceeds build volume boundaries at z=" << initial_layer_print_height << " mm"; - } - // Extrude the skirt loop. ExtrusionLoop eloop(elrSkirt); eloop.paths.emplace_back(ExtrusionPath( diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 698f3223e6..7261bc2ce6 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -970,23 +970,6 @@ public: static StringObjectException sequential_print_clearance_valid(const Print &print, Polygons *polygons = nullptr, std::vector>* height_polygons = nullptr); ConflictResultOpt get_conflict_result() const { return m_conflict_result; } - // Snapmaker: boundary violations tracking - void add_boundary_violation(const ConflictResult& violation) { - m_boundary_violations.push_back(violation); - } - - const std::vector& get_boundary_violations() const { - return m_boundary_violations; - } - - void clear_boundary_violations() { - m_boundary_violations.clear(); - } - - bool has_boundary_violations() const { - return !m_boundary_violations.empty(); - } - // Return 4 wipe tower corners in the world coordinates (shifted and rotated), including the wipe tower brim. Points first_layer_wipe_tower_corners(bool check_wipe_tower_existance=true) const; @@ -1078,8 +1061,6 @@ private: int m_modified_count {0}; //BBS ConflictResultOpt m_conflict_result; - //Snapmaker: boundary violations tracking - std::vector m_boundary_violations; FakeWipeTower m_fake_wipe_tower; //SoftFever: calibration diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b3806a1536..c76e1e2520 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2,9 +2,6 @@ #include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" -#include "BoundaryValidator.hpp" -#include "BuildVolume.hpp" -#include "GCode/GCodeProcessor.hpp" #include "ElephantFootCompensation.hpp" #include "Geometry.hpp" #include "I18N.hpp" @@ -673,57 +670,6 @@ void PrintObject::generate_support_material() this->_generate_support_material(); m_print->throw_if_canceled(); - - // Snapmaker: Validate support material against build volume boundaries - if (!m_support_layers.empty()) { - BuildVolume build_volume(m_print->config().printable_area.values, m_print->config().printable_height); - BuildVolumeBoundaryValidator validator(build_volume); - - for (const SupportLayer* layer : m_support_layers) { - // Check support fills polygons - for (const Polygon& support_contour : layer->support_fills.polygons_covered_by_spacing()) { - // Apply instance transforms and check boundary - for (const PrintInstance& instance : this->instances()) { - Polygon translated_contour = support_contour; - translated_contour.translate(instance.shift); - - // Convert to unscaled coordinates for validation - Vec3d plate_origin = m_print->get_plate_origin(); - double z_height = layer->print_z; - - // Check if any point of the contour exceeds boundaries - bool has_violation = false; - Vec3d violation_pos; - for (const Point& pt : translated_contour.points) { - Vec3d pt_unscaled( - unscale(pt.x()) + plate_origin.x(), - unscale(pt.y()) + plate_origin.y(), - z_height - ); - if (!validator.validate_point(pt_unscaled)) { - has_violation = true; - violation_pos = pt_unscaled; - break; - } - } - - if (has_violation) { - ConflictResult violation = ConflictResult::create_boundary_violation( - static_cast(BoundaryValidator::ViolationType::Support), - violation_pos, - z_height, - this->model_object()->name - ); - m_print->add_boundary_violation(violation); - BOOST_LOG_TRIVIAL(warning) << "Support material for object " << this->model_object()->name - << " exceeds build volume boundaries at z=" << z_height << " mm"; - // Only report once per layer to avoid spam - break; - } - } - } - } - } } this->set_done(posSupportMaterial); } diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index 685e96e00d..7580976ac9 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -8,9 +8,6 @@ #include "Geometry.hpp" #include "Point.hpp" #include "MutablePolygon.hpp" -#include "BoundaryValidator.hpp" -#include "BuildVolume.hpp" -#include "GCode/GCodeProcessor.hpp" #include #include @@ -584,91 +581,6 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) } #endif /* SLIC3R_DEBUG */ - // Snapmaker: Validate support polygons against build volume boundaries - // This addresses vulnerability #6: Support material boundary validation - BOOST_LOG_TRIVIAL(info) << "Support generator - Validating boundaries"; - BuildVolume build_volume(object.print()->config().printable_area.values, - object.print()->config().printable_height); - BuildVolumeBoundaryValidator validator(build_volume); - - int support_violations = 0; - for (const SupportLayer* layer : object.support_layers()) { - if (!layer) - continue; - - // Check support extrusions - const ExtrusionEntityCollection& support_fills = layer->support_fills; - for (const ExtrusionEntity* entity : support_fills.entities) { - if (!entity) - continue; - - // Check each extrusion path or loop - if (const ExtrusionPath* path = dynamic_cast(entity)) { - // Check each point in the polyline - for (const Point& pt : path->polyline.points) { - Vec3d pos(unscaled(pt.x()), unscaled(pt.y()), layer->print_z); - if (!validator.validate_point(pos)) { - support_violations++; - if (support_violations <= 5) { // Log first 5 - BOOST_LOG_TRIVIAL(warning) << "Support path at z=" << layer->print_z - << " exceeds build volume boundaries"; - } - break; // Only record once per path - } - } - } else if (const ExtrusionLoop* loop = dynamic_cast(entity)) { - for (const ExtrusionPath& path : loop->paths) { - // Check each point in the polyline - for (const Point& pt : path.polyline.points) { - Vec3d pos(unscaled(pt.x()), unscaled(pt.y()), layer->print_z); - if (!validator.validate_point(pos)) { - support_violations++; - if (support_violations <= 5) { // Log first 5 - BOOST_LOG_TRIVIAL(warning) << "Support loop path at z=" << layer->print_z - << " exceeds build volume boundaries"; - } - break; // Only record once per path - } - } - } - } - } - - // Check support base polygons - for (const ExPolygon& expoly : layer->lslices) { - if (!validator.validate_polygon(expoly.contour, layer->print_z)) { - support_violations++; - if (support_violations <= 5) { // Log first 5 - BOOST_LOG_TRIVIAL(warning) << "Support polygon at z=" << layer->print_z - << " exceeds build volume boundaries"; - } - } - // Check holes - for (const Polygon& hole : expoly.holes) { - if (!validator.validate_polygon(hole, layer->print_z)) { - support_violations++; - if (support_violations <= 5) { // Log first 5 - BOOST_LOG_TRIVIAL(warning) << "Support hole polygon at z=" << layer->print_z - << " exceeds build volume boundaries"; - } - } - } - } - } - - if (support_violations > 0) { - BOOST_LOG_TRIVIAL(warning) << "Found " << support_violations - << " support polygons/paths exceeding build volume boundaries"; - // Record violation - ConflictResult violation = ConflictResult::create_boundary_violation( - static_cast(BoundaryValidator::ViolationType::Support), - Vec3d(object.center_offset().x(), object.center_offset().y(), 0.0), - 0.0, - object.model_object()->name - ); - object.print()->add_boundary_violation(violation); - } - BOOST_LOG_TRIVIAL(info) << "Support generator - End"; } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 342d970777..4ad9b95654 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -2399,100 +2399,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result, const { //BBS: use convex_hull for toolpath outside check m_contained_in_bed = build_volume.all_paths_inside(gcode_result, m_paths_bounding_box); - - // BBS: Enhanced Travel move checking with smart filtering - // Skip initial setup moves (G28, G29) and only check moves during actual printing - if (m_contained_in_bed) { - // Find first extrusion move to determine where actual printing starts - size_t first_print_move = 0; - for (size_t i = 0; i < gcode_result.moves.size(); ++i) { - if (gcode_result.moves[i].type == EMoveType::Extrude && - gcode_result.moves[i].position.z() > 0.1) { // Above first layer - first_print_move = i; - break; - } - } - - // Only check moves after printing starts (skip G28/G29 initialization) - if (first_print_move > 0) { - bool has_travel_violations = false; - int violation_count = 0; - // Use same epsilon as BuildVolume::all_paths_inside() for consistency - static constexpr const double epsilon = BuildVolume::BedEpsilon; - - // Clear existing violations and populate with detailed info - auto& gcode_result_writable = const_cast(gcode_result); - gcode_result_writable.boundary_violations.clear(); - - for (size_t i = first_print_move; i < gcode_result.moves.size(); ++i) { - const auto& move = gcode_result.moves[i]; - - // Only check Travel moves (Extrude already checked by all_paths_inside) - if (move.type == EMoveType::Travel) { - // Quick rectangle bed check with tolerance - auto bbox = build_volume.bounding_volume(); - if (move.position.x() < bbox.min.x() - epsilon || - move.position.x() > bbox.max.x() + epsilon || - move.position.y() < bbox.min.y() - epsilon || - move.position.y() > bbox.max.y() + epsilon) { - - // Create detailed violation info - GCodeProcessorResult::BoundaryViolationInfo violation; - violation.violation_type = BoundaryValidator::ViolationType::TravelMove; - violation.position = Vec3d(move.position.x(), move.position.y(), move.position.z()); - violation.print_z = move.position.z(); - violation.component_name = "Travel"; - - // Determine which boundary was exceeded - double dist_x_min = bbox.min.x() - move.position.x(); - double dist_x_max = move.position.x() - bbox.max.x(); - double dist_y_min = bbox.min.y() - move.position.y(); - double dist_y_max = move.position.y() - bbox.max.y(); - - if (dist_x_min > 0) { - violation.direction = BoundaryValidator::BoundaryDirection::X_Min; - violation.distance_out = dist_x_min; - } else if (dist_x_max > 0) { - violation.direction = BoundaryValidator::BoundaryDirection::X_Max; - violation.distance_out = dist_x_max; - } else if (dist_y_min > 0) { - violation.direction = BoundaryValidator::BoundaryDirection::Y_Min; - violation.distance_out = dist_y_min; - } else if (dist_y_max > 0) { - violation.direction = BoundaryValidator::BoundaryDirection::Y_Max; - violation.distance_out = dist_y_max; - } - - gcode_result_writable.boundary_violations.push_back(violation); - - violation_count++; - if (violation_count <= 3) { // Log first 3 - std::string dir_str; - switch (violation.direction) { - case BoundaryValidator::BoundaryDirection::X_Min: dir_str = "X_min"; break; - case BoundaryValidator::BoundaryDirection::X_Max: dir_str = "X_max"; break; - case BoundaryValidator::BoundaryDirection::Y_Min: dir_str = "Y_min"; break; - case BoundaryValidator::BoundaryDirection::Y_Max: dir_str = "Y_max"; break; - default: dir_str = "Unknown"; break; - } - BOOST_LOG_TRIVIAL(warning) << "Travel move #" << i - << " outside bounds: " << violation.component_name << " " << dir_str - << " at pos=(" << move.position.x() - << ", " << move.position.y() << ", " << move.position.z() << ")"; - } - has_travel_violations = true; - } - } - } - - if (has_travel_violations) { - BOOST_LOG_TRIVIAL(warning) << "Found " << violation_count - << " Travel moves outside build volume"; - m_contained_in_bed = false; - } - } - } - if (m_contained_in_bed) { //PartPlateList& partplate_list = wxGetApp().plater()->get_partplate_list(); //PartPlate* plate = partplate_list.get_curr_plate(); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 79242a06ab..94a7b0bc43 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -849,12 +849,6 @@ public: //BBS: add only gcode mode bool is_only_gcode_in_preview() const { return m_only_gcode_in_preview; } - // Snapmaker: Get boundary violations from gcode_result - const std::vector& get_boundary_violations() const { - static const std::vector empty_violations; - return (m_gcode_result != nullptr) ? m_gcode_result->boundary_violations : empty_violations; - } - EViewType get_view_type() const { return m_view_type; } void set_view_type(EViewType type, bool reset_feature_type_visible = true) { if (type == EViewType::Count) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ac1e4202be..a78ff2d68f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -9687,102 +9687,7 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) } case EWarning::ObjectOutside: text = _u8L("An object is laid over the plate boundaries."); break; case EWarning::ToolHeightOutside: text = _u8L("A G-code path goes beyond the max print height."); error = ErrorType::SLICING_ERROR; break; - case EWarning::ToolpathOutside: { - error = ErrorType::SLICING_ERROR; - // Snapmaker: Enhanced boundary violation reporting with detailed information - static std::string prevBoundaryText; - text = prevBoundaryText; - - // Helper function to get localized violation type name - auto get_localized_type_string = [](BoundaryValidator::ViolationType type) -> std::string { - switch (type) { - case BoundaryValidator::ViolationType::TravelMove: return _u8L("Travel Move"); - case BoundaryValidator::ViolationType::ExtrudeMove: return _u8L("Extrude Move"); - case BoundaryValidator::ViolationType::SpiralLift: return _u8L("Spiral Lift"); - case BoundaryValidator::ViolationType::LazyLift: return _u8L("Lazy Lift"); - case BoundaryValidator::ViolationType::WipeTower: return _u8L("Wipe Tower"); - case BoundaryValidator::ViolationType::Skirt: return _u8L("Skirt"); - case BoundaryValidator::ViolationType::Brim: return _u8L("Brim"); - case BoundaryValidator::ViolationType::Support: return _u8L("Support"); - case BoundaryValidator::ViolationType::ArcMove: return _u8L("Arc Move"); - default: return _u8L("Unknown"); - } - }; - - // Helper function to get localized direction string - auto get_localized_direction_string = [](BoundaryValidator::BoundaryDirection dir) -> std::string { - switch (dir) { - case BoundaryValidator::BoundaryDirection::X_Min: return _u8L("beyond X minimum"); - case BoundaryValidator::BoundaryDirection::X_Max: return _u8L("beyond X maximum"); - case BoundaryValidator::BoundaryDirection::Y_Min: return _u8L("beyond Y minimum"); - case BoundaryValidator::BoundaryDirection::Y_Max: return _u8L("beyond Y maximum"); - case BoundaryValidator::BoundaryDirection::Z_Max: return _u8L("above Z maximum"); - case BoundaryValidator::BoundaryDirection::Radius: return _u8L("beyond bed radius"); - default: return _u8L("outside boundaries"); - } - }; - - // Try to get detailed violation information from gcode_result - const auto& violations = m_gcode_viewer.get_boundary_violations(); - if (!violations.empty()) { - - // Group violations by type for better summary - std::map violation_counts; - std::map type_names; - for (const auto& v : violations) { - violation_counts[v.violation_type]++; - if (type_names.find(v.violation_type) == type_names.end()) { - type_names[v.violation_type] = get_localized_type_string(v.violation_type); - } - } - - // Build detailed message - std::string msg = _u8L("G-code boundary violations detected:\n\n"); - int total_count = 0; - for (const auto& [type, count] : violation_counts) { - msg += "• " + type_names[type] + ": " + std::to_string(count) + " " + _u8L("violation(s)") + "\n"; - total_count += count; - } - - if (total_count > 1) { - msg += "\n" + _u8L("Total") + ": " + std::to_string(total_count) + " " + _u8L("violations"); - } - - // Show details of first few violations - if (!violations.empty()) { - msg += "\n\n" + _u8L("Details") + ":\n"; - int show_count = std::min((int)violations.size(), 5); - for (int i = 0; i < show_count; ++i) { - const auto& v = violations[i]; - // Build localized description - std::string desc; - if (!v.component_name.empty()) { - desc += v.component_name + " - "; - } - desc += get_localized_type_string(v.violation_type); - desc += " " + get_localized_direction_string(v.direction); - msg += " " + std::to_string(i + 1) + ". " + desc; - if (v.distance_out > 0.001) { - msg += " (" + (boost::format(_u8L("%.2f mm out")) % v.distance_out).str() + ")"; - } - if (v.print_z > 0) { - msg += " " + _u8L("at Z") + "=" + (boost::format("%.1f") % v.print_z).str(); - } - msg += "\n"; - } - if ((int)violations.size() > show_count) { - msg += " " + _u8L("... and more"); - } - } - - text = msg; - prevBoundaryText = text; - } else { - // Fallback to generic message if no detailed info available - text = _u8L("A G-code path goes beyond the plate boundaries."); - } - break; - } + case EWarning::ToolpathOutside: text = _u8L("A G-code path goes beyond the plate boundaries."); error = ErrorType::SLICING_ERROR; break; // BBS: remove _u8L() for SLA case EWarning::SlaSupportsOutside: text = ("SLA supports outside the print area were detected."); error = ErrorType::PLATER_ERROR; break; case EWarning::SomethingNotShown: text = _u8L("Only the object being edited is visible."); break; diff --git a/tools/README_gcode_checker.md b/tools/README_gcode_checker.md deleted file mode 100644 index 04bb8708cd..0000000000 --- a/tools/README_gcode_checker.md +++ /dev/null @@ -1,312 +0,0 @@ -# G-code边界超限检查工具 - 使用说明 - -## 📋 工具简介 - -这是一个带有图形界面的G-code边界检查工具,可以帮助你: - -✅ **检测Travel移动超限** - 发现可能导致打印头撞机的Travel移动 -✅ **检测Extrude移动超限** - 发现挤出路径超出边界 -✅ **支持多种床类型** - 矩形床、圆形床(Delta打印机) -✅ **详细报告** - 提供超限位置、类型、距离等详细信息 -✅ **快速预设** - 常见打印机尺寸一键设置 - ---- - -## 🚀 快速开始 - -### 方法1: 双击启动(推荐) - -1. 双击 `run_gcode_checker.bat` 启动程序 -2. 如果提示"未找到Python",需要先安装Python(见下方) - -### 方法2: 命令行启动 - -```bash -python gcode_boundary_checker_gui.py -``` - ---- - -## 💻 系统要求 - -- **Python 3.7+**(必需) -- **tkinter**(Python标准库,通常自带) -- 支持 Windows / macOS / Linux - -### 安装Python - -如果系统没有Python,请访问:https://www.python.org/downloads/ - -**Windows用户注意**:安装时勾选 "Add Python to PATH" - -验证安装: -```bash -python --version -# 应显示: Python 3.x.x -``` - ---- - -## 📖 使用教程 - -### 步骤1: 选择G-code文件 - -点击"浏览..."按钮,选择要检查的`.gcode`文件 - -### 步骤2: 配置床参数 - -#### 矩形床(常见3D打印机) - -1. 选择"矩形床" -2. 输入尺寸: - - **X**: 床宽度(mm) - - **Y**: 床深度(mm) - - **Z**: 最大打印高度(mm) -3. 原点通常保持 (0, 0) - -**快速预设**(点击即可应用): -- `200×200×250` - Ender 3, CR-10等 -- `220×220×250` - Prusa i3 MK3等 -- `250×250×300` - CR-10S等 -- `300×300×400` - CR-10 Max等 - -#### 圆形床(Delta打印机) - -1. 选择"圆形床 (Delta)" -2. 输入: - - **半径**: 床半径(mm) - - **Z高度**: 最大打印高度(mm) - -### 步骤3: 开始分析 - -1. 点击"开始分析"按钮 -2. 等待进度条完成(大文件可能需要几秒钟) -3. 查看结果报告 - -### 步骤4: 查看结果 - -#### ✅ 正常情况 -``` -✅ 所有移动都在边界内! -``` - -#### ⚠️ 发现超限 -报告会显示: -- 总超限数量 -- Travel/Extrude超限分类 -- 超限类型统计 -- 详细超限列表(前100个) - -每个超限包含: -- **行号**: G-code文件中的行数 -- **类型**: Travel或Extrude -- **位置**: X, Y, Z坐标 -- **超限类型**: X/Y/Z超限方向 -- **超出距离**: 超出边界多少mm -- **原始代码**: 超限的G-code命令 - -### 步骤5: 保存报告 - -点击"保存报告"按钮,将完整报告保存为`.txt`文件 - ---- - -## 📊 报告示例 - -``` -====================================================================== -G-code边界超限分析报告 -====================================================================== - -床类型: 矩形 -床边界: X[0.0, 200.0] Y[0.0, 200.0] Z[0, 250.0] - -总行数: 45823 -总移动数: 12456 - - Travel移动: 3421 - - Extrude移动: 9035 - -发现超限: 5 处 - - Travel超限: 3 - - Extrude超限: 2 - -超限类型统计: - X > 最大值: 3 次 - Y > 最大值: 2 次 - -====================================================================== -详细超限列表 (前100个): -====================================================================== - -[1] 行 1234: Travel - X > 最大值 - 位置: X=205.340 Y=100.000 Z=50.000 E=123.456 - 超出: 5.340 mm - 代码: G0 X205.34 Y100 F7200 - -[2] 行 2345: Extrude - Y > 最大值 - 位置: X=150.000 Y=203.120 Z=50.000 E=150.234 - 超出: 3.120 mm - 代码: G1 X150 Y203.12 E150.234 -``` - ---- - -## 🔍 常见问题 - -### Q1: 为什么会检测出Travel超限? - -**原因**: -- OrcaSlicer原有代码只检查Extrude移动,忽略了Travel移动 -- Travel移动如果超限,可能导致打印头撞击边界 - -**如何修复**: -1. 调整模型位置,远离床边缘 -2. 减小Skirt/Brim距离 -3. 检查擦料塔位置 -4. 调整打印顺序 - -### Q2: 显示"✅ 所有移动都在边界内",但切片软件仍报错? - -可能原因: -- 切片软件使用了更严格的边界检查 -- 考虑了挤出线宽(本工具只检查路径中心线) -- 其他非边界问题(如对象冲突) - -### Q3: 超限距离很小(如0.1mm),需要担心吗? - -**一般情况**: -- <0.5mm:通常是浮点误差,可能安全 -- 0.5-2mm:建议修复,有撞机风险 -- >2mm:必须修复 - -### Q4: 如何处理大量超限? - -**排查步骤**: -1. 检查床尺寸设置是否正确 -2. 检查模型是否整体偏移 -3. 检查切片配置(Skirt/Brim/Wipe Tower) -4. 使用切片软件自动排版 - -### Q5: 程序运行很慢 - -**优化建议**: -- 大文件(>100MB)可能需要1-2分钟 -- 关闭其他程序释放内存 -- Python版本建议3.9+(性能更好) - ---- - -## 🛠️ 高级用法 - -### 命令行版本 - -如果需要批量处理或集成到脚本,可以使用命令行版本: - -```bash -python analyze_gcode_bounds.py output.gcode --bed-size 200 200 250 -``` - -详细选项: -```bash -# 矩形床 -python analyze_gcode_bounds.py file.gcode --bed-size 200 200 250 - -# 矩形床 + 自定义原点 -python analyze_gcode_bounds.py file.gcode --bed-size 200 200 250 --bed-origin 10 10 - -# 圆形床 -python analyze_gcode_bounds.py file.gcode --bed-type circle --radius 100 --max-z 250 -``` - ---- - -## 🐛 故障排除 - -### 错误: "未找到Python" - -**解决方法**: -1. 安装Python 3.7+ -2. 确保安装时勾选"Add to PATH" -3. 重启命令提示符/终端 - -### 错误: "No module named 'tkinter'" - -**Windows**:重新安装Python,勾选"tcl/tk and IDLE" -**Linux**:`sudo apt-get install python3-tk` -**macOS**:通常自带,如缺失重新安装Python - -### 界面显示乱码 - -修改系统区域设置为中文,或使用命令行版本 - -### 程序崩溃或卡死 - -1. 检查G-code文件是否损坏 -2. 尝试用文本编辑器打开G-code -3. 更新Python到最新版本 - ---- - -## 📝 技术细节 - -### 检测算法 - -1. **矩形床**:检查每个移动点是否在 `[x_min, x_max] × [y_min, y_max] × [0, z_max]` 内 -2. **圆形床**:检查每个移动点到中心的距离是否 ≤ 半径 -3. **容差**:默认允许 0.01mm 误差(浮点精度) - -### 移动分类 - -- **Travel**: G0命令 或 G1命令且E值不变 -- **Extrude**: G1/G2/G3命令且E值增加 -- **Retract**: E值减少(不检查) - -### 性能 - -- 解析速度:约 50,000 行/秒(Python 3.9) -- 内存占用:约为文件大小的 2-3倍 -- 大文件(1GB+)建议使用命令行版本 - ---- - -## 📄 文件说明 - -| 文件 | 说明 | -|------|------| -| `gcode_boundary_checker_gui.py` | GUI版本(推荐) | -| `analyze_gcode_bounds.py` | 命令行版本 | -| `run_gcode_checker.bat` | Windows启动脚本 | -| `README_gcode_checker.md` | 本说明文档 | - ---- - -## 🔗 相关资源 - -- **OrcaSlicer修复文档**: `docs/gcode_boundary_optimization_implementation.md` -- **技术方案**: `docs/gcode_boundary_checking_optimization.md` -- **问题反馈**: https://github.com/Snapmaker/OrcaSlicer/issues - ---- - -## 📜 更新日志 - -### v1.0 (2026-01-19) -- ✅ 初始版本 -- ✅ 支持矩形床和圆形床 -- ✅ GUI界面 -- ✅ 详细报告生成 -- ✅ Travel/Extrude移动分类检测 - ---- - -## 🙏 致谢 - -本工具是OrcaSlicer边界检查优化项目的一部分,旨在帮助用户诊断和修复边界超限问题。 - -**项目编号**: ORCA-2026-001 -**创建日期**: 2026-01-19 -**作者**: Claude Code - ---- - -**祝你打印顺利! 🎉** diff --git a/tools/analyze_gcode_bounds.py b/tools/analyze_gcode_bounds.py deleted file mode 100644 index cd952cf2ac..0000000000 --- a/tools/analyze_gcode_bounds.py +++ /dev/null @@ -1,449 +0,0 @@ -#!/usr/bin/env python3 -""" -G-code边界超限分析工具 -Analyzes G-code files to find moves that exceed build volume boundaries. - -用法 / Usage: - python analyze_gcode_bounds.py [options] - -示例 / Examples: - python analyze_gcode_bounds.py output.gcode --bed-size 200 200 250 - python analyze_gcode_bounds.py output.gcode --bed-type circle --radius 100 -""" - -import re -import sys -import argparse -from enum import Enum -from dataclasses import dataclass -from typing import List, Tuple, Optional -import math - - -class BedType(Enum): - RECTANGLE = "rectangle" - CIRCLE = "circle" - -class MoveType(Enum): - TRAVEL = "Travel" - EXTRUDE = "Extrude" - ARC_CW = "Arc CW (G2)" # 顺时针弧线 - ARC_CCW = "Arc CCW (G3)" # 逆时针弧线 - RETRACT = "Retract" - UNKNOWN = "Unknown" - -class ViolationType(Enum): - X_MIN = "X < Min" - X_MAX = "X > Max" - Y_MIN = "Y < Min" - Y_MAX = "Y > Max" - Z_MAX = "Z > Max" - RADIUS = "Radius > Max (Circle bed)" - -@dataclass -class Position: - x: float = 0.0 - y: float = 0.0 - z: float = 0.0 - e: float = 0.0 - - def copy(self): - return Position(self.x, self.y, self.z, self.e) - -@dataclass -class Violation: - line_num: int - line_content: str - position: Position - move_type: MoveType - violation_types: List[ViolationType] - distance_out: float # 超出距离 (mm) - - def __str__(self): - vio_str = ", ".join([v.value for v in self.violation_types]) - return (f"Line {self.line_num}: {self.move_type.value} - {vio_str}\n" - f" Position: X={self.position.x:.3f} Y={self.position.y:.3f} " - f"Z={self.position.z:.3f} E={self.position.e:.3f}\n" - f" Out by: {self.distance_out:.3f} mm\n" - f" G-code: {self.line_content.strip()}") - - -class GCodeAnalyzer: - def __init__(self, bed_type: BedType, bed_min: Tuple[float, float], - bed_max: Tuple[float, float], max_z: float, radius: float = None): - self.bed_type = bed_type - self.bed_min = bed_min - self.bed_max = bed_max - self.max_z = max_z - self.radius = radius # For circle bed - self.center = ((bed_max[0] + bed_min[0]) / 2, - (bed_max[1] + bed_min[1]) / 2) if bed_type == BedType.CIRCLE else None - - self.current_pos = Position() - self.violations: List[Violation] = [] - - # 统计 - self.total_moves = 0 - self.travel_moves = 0 - self.extrude_moves = 0 - - def parse_gcode_file(self, filename: str): - """解析G-code文件""" - print(f"正在分析文件: {filename}") - print(f"床类型: {self.bed_type.value}") - - if self.bed_type == BedType.RECTANGLE: - print(f"床边界: X[{self.bed_min[0]:.1f}, {self.bed_max[0]:.1f}] " - f"Y[{self.bed_min[1]:.1f}, {self.bed_max[1]:.1f}] " - f"Z[0, {self.max_z:.1f}]") - else: - print(f"床中心: ({self.center[0]:.1f}, {self.center[1]:.1f})") - print(f"床半径: {self.radius:.1f} mm, Z[0, {self.max_z:.1f}]") - - print("=" * 70) - - try: - with open(filename, 'r', encoding='utf-8') as f: - for line_num, line in enumerate(f, 1): - self._parse_line(line_num, line) - except FileNotFoundError: - print(f"错误: 文件未找到 '{filename}'") - sys.exit(1) - except Exception as e: - print(f"错误: 读取文件时出错: {e}") - sys.exit(1) - - def _parse_line(self, line_num: int, line: str): - """解析单行G-code""" - # 移除注释 - if ';' in line: - code_part = line[:line.index(';')] - comment = line[line.index(';'):] - else: - code_part = line - comment = "" - - code_part = code_part.strip().upper() - if not code_part: - return - - # Check for G0/G1/G2/G3 commands (using word boundary to avoid matching G28, G29, etc.) - g_match = re.match(r'G([0-3])\b', code_part) - if not g_match: - return - - g_code = int(g_match.group(1)) - - # Parse coordinates - x_match = re.search(r'X([-+]?\d*\.?\d+)', code_part) - y_match = re.search(r'Y([-+]?\d*\.?\d+)', code_part) - z_match = re.search(r'Z([-+]?\d*\.?\d+)', code_part) - e_match = re.search(r'E([-+]?\d*\.?\d+)', code_part) - i_match = re.search(r'I([-+]?\d*\.?\d+)', code_part) - j_match = re.search(r'J([-+]?\d*\.?\d+)', code_part) - - # G2/G3 arc commands - if g_code in [2, 3] and (i_match or j_match): - self._parse_arc(line_num, line, code_part, g_code, x_match, y_match, - z_match, e_match, i_match, j_match) - return - - # G0/G1 linear moves - new_pos = self.current_pos.copy() - has_move = False - has_xy_move = False - - if x_match: - new_pos.x = float(x_match.group(1)) - has_move = True - has_xy_move = True - if y_match: - new_pos.y = float(y_match.group(1)) - has_move = True - has_xy_move = True - if z_match: - new_pos.z = float(z_match.group(1)) - has_move = True - if e_match: - new_pos.e = float(e_match.group(1)) - - if not has_move: - return - - # 判断移动类型 - move_type = self._classify_move(code_part, self.current_pos, new_pos) - - if move_type == MoveType.TRAVEL: - self.travel_moves += 1 - elif move_type == MoveType.EXTRUDE: - self.extrude_moves += 1 - self.total_moves += 1 - - # 检查边界 - Only check XY bounds if X or Y actually moved - if has_xy_move: - violations = self._check_bounds(new_pos) - if violations: - distance = self._calculate_distance_out(new_pos) - self.violations.append(Violation( - line_num=line_num, - line_content=line, - position=new_pos.copy(), - move_type=move_type, - violation_types=violations, - distance_out=distance - )) - - self.current_pos = new_pos - - def _parse_arc(self, line_num: int, line: str, code_part: str, g_code: int, - x_match, y_match, z_match, e_match, i_match, j_match): - """Parse G2/G3 arc commands and check arc path for boundary violations""" - start_x = self.current_pos.x - start_y = self.current_pos.y - start_z = self.current_pos.z - - i = float(i_match.group(1)) if i_match else 0.0 - j = float(j_match.group(1)) if j_match else 0.0 - - center_x = start_x + i - center_y = start_y + j - radius = math.sqrt(i * i + j * j) - - end_x = float(x_match.group(1)) if x_match else None - end_y = float(y_match.group(1)) if y_match else None - end_z = float(z_match.group(1)) if z_match else start_z - e = float(e_match.group(1)) if e_match else self.current_pos.e - - if end_x is None and end_y is None: - end_angle = math.atan2(start_y - center_y, start_x - center_x) + (2 * math.pi if g_code == 3 else -2 * math.pi) - end_x = center_x + radius * math.cos(end_angle) - end_y = center_y + radius * math.sin(end_angle) - elif end_x is None: - end_x = start_x - elif end_y is None: - end_y = start_y - - start_angle = math.atan2(start_y - center_y, start_x - center_x) - end_angle = math.atan2(end_y - center_y, end_x - center_x) - - if g_code == 2: - if end_angle > start_angle: - end_angle -= 2 * math.pi - angle_sweep = start_angle - end_angle - else: - if end_angle < start_angle: - end_angle += 2 * math.pi - angle_sweep = end_angle - start_angle - - num_samples = max(8, int(abs(angle_sweep) * radius / 5)) - - move_type = MoveType.ARC_CCW if g_code == 3 else MoveType.ARC_CW - if move_type == MoveType.ARC_CW: - self.travel_moves += 1 - else: - self.extrude_moves += 1 - self.total_moves += 1 - - for n in range(num_samples + 1): - t = n / num_samples - angle = start_angle + (angle_sweep * t if g_code == 3 else -angle_sweep * t) - - sample_x = center_x + radius * math.cos(angle) - sample_y = center_y + radius * math.sin(angle) - sample_z = start_z + (end_z - start_z) * t - - sample_pos = Position(sample_x, sample_y, sample_z, e) - violations = self._check_bounds(sample_pos) - - if violations: - distance = self._calculate_distance_out(sample_pos) - self.violations.append(Violation( - line_num=line_num, - line_content=line, - position=sample_pos, - move_type=move_type, - violation_types=violations, - distance_out=distance - )) - break - - self.current_pos.x = end_x - self.current_pos.y = end_y - self.current_pos.z = end_z - self.current_pos.e = e - - def _classify_move(self, code: str, old_pos: Position, new_pos: Position) -> MoveType: - """分类移动类型""" - # G0 通常是快速移动(Travel) - if code.startswith('G0'): - return MoveType.TRAVEL - - # G1 可能是Travel或Extrude,看E值 - if code.startswith('G1'): - if abs(new_pos.e - old_pos.e) > 0.001: # 有挤出 - return MoveType.EXTRUDE - else: - return MoveType.TRAVEL - - # G2/G3 是弧线,通常是挤出 - if code.startswith('G2') or code.startswith('G3'): - return MoveType.EXTRUDE - - return MoveType.UNKNOWN - - def _check_bounds(self, pos: Position) -> List[ViolationType]: - """检查坐标是否超出边界""" - violations = [] - epsilon = 0.01 # 允许的误差 - - if self.bed_type == BedType.RECTANGLE: - if pos.x < self.bed_min[0] - epsilon: - violations.append(ViolationType.X_MIN) - if pos.x > self.bed_max[0] + epsilon: - violations.append(ViolationType.X_MAX) - if pos.y < self.bed_min[1] - epsilon: - violations.append(ViolationType.Y_MIN) - if pos.y > self.bed_max[1] + epsilon: - violations.append(ViolationType.Y_MAX) - - elif self.bed_type == BedType.CIRCLE: - dist = math.sqrt((pos.x - self.center[0])**2 + (pos.y - self.center[1])**2) - if dist > self.radius + epsilon: - violations.append(ViolationType.RADIUS) - - # Z轴检查 - if self.max_z > 0 and pos.z > self.max_z + epsilon: - violations.append(ViolationType.Z_MAX) - - return violations - - def _calculate_distance_out(self, pos: Position) -> float: - """计算超出边界的距离""" - if self.bed_type == BedType.RECTANGLE: - dx = max(0, self.bed_min[0] - pos.x, pos.x - self.bed_max[0]) - dy = max(0, self.bed_min[1] - pos.y, pos.y - self.bed_max[1]) - dz = max(0, pos.z - self.max_z) if self.max_z > 0 else 0 - return math.sqrt(dx**2 + dy**2 + dz**2) - - elif self.bed_type == BedType.CIRCLE: - dist = math.sqrt((pos.x - self.center[0])**2 + (pos.y - self.center[1])**2) - return max(0, dist - self.radius) - - return 0.0 - - def print_report(self): - """打印分析报告""" - print("\n" + "=" * 70) - print("分析报告 / Analysis Report") - print("=" * 70) - - print(f"\n总移动数: {self.total_moves}") - print(f" - Travel移动: {self.travel_moves}") - print(f" - Extrude移动: {self.extrude_moves}") - print(f" - 其他: {self.total_moves - self.travel_moves - self.extrude_moves}") - - print(f"\n发现超限: {len(self.violations)} 处") - - if not self.violations: - print("\n✅ 所有移动都在边界内!") - return - - # 按类型分组 - travel_violations = [v for v in self.violations if v.move_type == MoveType.TRAVEL] - extrude_violations = [v for v in self.violations if v.move_type == MoveType.EXTRUDE] - - print(f" - Travel超限: {len(travel_violations)}") - print(f" - Extrude超限: {len(extrude_violations)}") - - # 按超限类型统计 - print("\n超限类型统计:") - from collections import Counter - all_vio_types = [] - for v in self.violations: - all_vio_types.extend(v.violation_types) - vio_counter = Counter(all_vio_types) - for vio_type, count in vio_counter.most_common(): - print(f" {vio_type.value}: {count} 次") - - # 详细列出超限 - print("\n" + "=" * 70) - print("详细超限列表 (前50个):") - print("=" * 70) - - for i, violation in enumerate(self.violations[:50], 1): - print(f"\n[{i}] {violation}") - - if len(self.violations) > 50: - print(f"\n... 还有 {len(self.violations) - 50} 个超限未显示") - - # 保存到文件 - output_file = "gcode_violations.txt" - with open(output_file, 'w', encoding='utf-8') as f: - f.write("G-code边界超限详细报告\n") - f.write("=" * 70 + "\n\n") - - for i, violation in enumerate(self.violations, 1): - f.write(f"[{i}] {violation}\n\n") - - print(f"\n完整报告已保存到: {output_file}") - - -def main(): - parser = argparse.ArgumentParser( - description='分析G-code文件的边界超限问题', - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -示例: - # 矩形床 200x200x250mm - python %(prog)s output.gcode --bed-size 200 200 250 - - # 矩形床,指定原点偏移 - python %(prog)s output.gcode --bed-size 200 200 250 --bed-origin 0 0 - - # 圆形床(如Delta打印机) - python %(prog)s output.gcode --bed-type circle --radius 100 --max-z 250 - """ - ) - - parser.add_argument('gcode_file', help='G-code文件路径') - parser.add_argument('--bed-type', choices=['rectangle', 'circle'], - default='rectangle', help='床类型 (默认: rectangle)') - parser.add_argument('--bed-size', type=float, nargs=3, metavar=('X', 'Y', 'Z'), - help='床尺寸 X Y Z (mm), 例如: 200 200 250') - parser.add_argument('--bed-origin', type=float, nargs=2, metavar=('X', 'Y'), - default=(0, 0), help='床原点坐标 (默认: 0 0)') - parser.add_argument('--radius', type=float, help='圆形床半径 (mm)') - parser.add_argument('--max-z', type=float, help='最大Z高度 (mm)') - - args = parser.parse_args() - - # 解析床参数 - bed_type = BedType(args.bed_type) - - if bed_type == BedType.RECTANGLE: - if not args.bed_size: - print("错误: 矩形床需要指定 --bed-size") - sys.exit(1) - bed_min = (args.bed_origin[0], args.bed_origin[1]) - bed_max = (args.bed_origin[0] + args.bed_size[0], - args.bed_origin[1] + args.bed_size[1]) - max_z = args.bed_size[2] - radius = None - - elif bed_type == BedType.CIRCLE: - if not args.radius or not args.max_z: - print("错误: 圆形床需要指定 --radius 和 --max-z") - sys.exit(1) - bed_min = (-args.radius, -args.radius) - bed_max = (args.radius, args.radius) - max_z = args.max_z - radius = args.radius - - # 分析G-code - analyzer = GCodeAnalyzer(bed_type, bed_min, bed_max, max_z, radius) - analyzer.parse_gcode_file(args.gcode_file) - analyzer.print_report() - - -if __name__ == '__main__': - main() diff --git a/tools/gcode_boundary_checker_gui.py b/tools/gcode_boundary_checker_gui.py deleted file mode 100644 index 675c69bdba..0000000000 --- a/tools/gcode_boundary_checker_gui.py +++ /dev/null @@ -1,674 +0,0 @@ -#!/usr/bin/env python3 -""" -G-code边界超限检查工具 - GUI版本 -G-code Boundary Violation Checker - GUI Version - -带有图形界面的G-code边界检测工具 -""" - -import tkinter as tk -from tkinter import ttk, filedialog, messagebox, scrolledtext -import re -import math -from enum import Enum -from dataclasses import dataclass -from typing import List, Tuple -import threading -from pathlib import Path - - -class BedType(Enum): - RECTANGLE = "矩形床 (Rectangle)" - CIRCLE = "圆形床 (Circle)" - - -class MoveType(Enum): - TRAVEL = "Travel" - EXTRUDE = "Extrude" - ARC_CW = "Arc CW (G2)" # 顺时针弧线 - ARC_CCW = "Arc CCW (G3)" # 逆时针弧线 - RETRACT = "Retract" - UNKNOWN = "Unknown" - - -class ViolationType(Enum): - X_MIN = "X < 最小值" - X_MAX = "X > 最大值" - Y_MIN = "Y < 最小值" - Y_MAX = "Y > 最大值" - Z_MAX = "Z > 最大值" - RADIUS = "半径超限 (圆形床)" - - -@dataclass -class Position: - x: float = 0.0 - y: float = 0.0 - z: float = 0.0 - e: float = 0.0 - - def copy(self): - return Position(self.x, self.y, self.z, self.e) - - -@dataclass -class Violation: - line_num: int - line_content: str - position: Position - move_type: MoveType - violation_types: List[ViolationType] - distance_out: float - - def __str__(self): - vio_str = ", ".join([v.value for v in self.violation_types]) - return (f"行 {self.line_num}: {self.move_type.value} - {vio_str}\n" - f" 位置: X={self.position.x:.3f} Y={self.position.y:.3f} " - f"Z={self.position.z:.3f} E={self.position.e:.3f}\n" - f" 超出: {self.distance_out:.3f} mm\n" - f" 代码: {self.line_content.strip()}") - - -class GCodeAnalyzer: - def __init__(self, bed_type: BedType, bed_min: Tuple[float, float], - bed_max: Tuple[float, float], max_z: float, radius: float = None, - progress_callback=None): - self.bed_type = bed_type - self.bed_min = bed_min - self.bed_max = bed_max - self.max_z = max_z - self.radius = radius - self.center = ((bed_max[0] + bed_min[0]) / 2, - (bed_max[1] + bed_min[1]) / 2) if bed_type == BedType.CIRCLE else None - self.progress_callback = progress_callback - - self.current_pos = Position() - self.violations: List[Violation] = [] - self.total_moves = 0 - self.travel_moves = 0 - self.extrude_moves = 0 - self.total_lines = 0 - - def parse_gcode_file(self, filename: str): - """解析G-code文件""" - try: - # 先计算总行数 - with open(filename, 'r', encoding='utf-8') as f: - self.total_lines = sum(1 for _ in f) - - # 解析文件 - with open(filename, 'r', encoding='utf-8') as f: - for line_num, line in enumerate(f, 1): - self._parse_line(line_num, line) - - # 更新进度 - if self.progress_callback and line_num % 100 == 0: - progress = (line_num / self.total_lines) * 100 - self.progress_callback(progress, line_num, self.total_lines) - - return True - except Exception as e: - return str(e) - - def _parse_line(self, line_num: int, line: str): - """解析单行G-code""" - if ';' in line: - code_part = line[:line.index(';')] - else: - code_part = line - - code_part = code_part.strip().upper() - if not code_part: - return - - # Check for G0/G1/G2/G3 commands (using word boundary to avoid matching G28, G29, etc.) - g_match = re.match(r'G([0-3])\b', code_part) - if not g_match: - return - - g_code = int(g_match.group(1)) - - # Parse coordinates - x_match = re.search(r'X([-+]?\d*\.?\d+)', code_part) - y_match = re.search(r'Y([-+]?\d*\.?\d+)', code_part) - z_match = re.search(r'Z([-+]?\d*\.?\d+)', code_part) - e_match = re.search(r'E([-+]?\d*\.?\d+)', code_part) - i_match = re.search(r'I([-+]?\d*\.?\d+)', code_part) - j_match = re.search(r'J([-+]?\d*\.?\d+)', code_part) - - # G2/G3 arc commands - if g_code in [2, 3] and (i_match or j_match): - self._parse_arc(line_num, line, code_part, g_code, x_match, y_match, - z_match, e_match, i_match, j_match) - return - - # G0/G1 linear moves - new_pos = self.current_pos.copy() - has_move = False - has_xy_move = False - - if x_match: - new_pos.x = float(x_match.group(1)) - has_move = True - has_xy_move = True - if y_match: - new_pos.y = float(y_match.group(1)) - has_move = True - has_xy_move = True - if z_match: - new_pos.z = float(z_match.group(1)) - has_move = True - if e_match: - new_pos.e = float(e_match.group(1)) - - if not has_move: - return - - move_type = self._classify_move(code_part, self.current_pos, new_pos) - - if move_type == MoveType.TRAVEL: - self.travel_moves += 1 - elif move_type == MoveType.EXTRUDE: - self.extrude_moves += 1 - self.total_moves += 1 - - # Only check XY bounds if X or Y actually moved - if has_xy_move: - violations = self._check_bounds(new_pos) - if violations: - distance = self._calculate_distance_out(new_pos) - self.violations.append(Violation( - line_num=line_num, - line_content=line, - position=new_pos.copy(), - move_type=move_type, - violation_types=violations, - distance_out=distance - )) - - self.current_pos = new_pos - - def _parse_arc(self, line_num: int, line: str, code_part: str, g_code: int, - x_match, y_match, z_match, e_match, i_match, j_match): - """Parse G2/G3 arc commands and check arc path for boundary violations""" - # Current position is the start of the arc - start_x = self.current_pos.x - start_y = self.current_pos.y - start_z = self.current_pos.z - - # Parse I, J (offsets from start to center) - i = float(i_match.group(1)) if i_match else 0.0 - j = float(j_match.group(1)) if j_match else 0.0 - - # Calculate arc center - center_x = start_x + i - center_y = start_y + j - radius = math.sqrt(i * i + j * j) - - # Parse X, Y if present (end point) - end_x = float(x_match.group(1)) if x_match else None - end_y = float(y_match.group(1)) if y_match else None - end_z = float(z_match.group(1)) if z_match else start_z - e = float(e_match.group(1)) if e_match else self.current_pos.e - - # If no X/Y specified, do a full circle (360 degrees) - if end_x is None and end_y is None: - # For full circle, calculate end point as start point - end_angle = math.atan2(start_y - center_y, start_x - center_x) + (2 * math.pi if g_code == 3 else -2 * math.pi) - end_x = center_x + radius * math.cos(end_angle) - end_y = center_y + radius * math.sin(end_angle) - elif end_x is None: - end_x = start_x - elif end_y is None: - end_y = start_y - - # Calculate start and end angles - start_angle = math.atan2(start_y - center_y, start_x - center_x) - end_angle = math.atan2(end_y - center_y, end_x - center_x) - - # Determine arc direction and angle sweep - if g_code == 2: # Clockwise - if end_angle > start_angle: - end_angle -= 2 * math.pi - angle_sweep = start_angle - end_angle - else: # G3: Counter-clockwise - if end_angle < start_angle: - end_angle += 2 * math.pi - angle_sweep = end_angle - start_angle - - # Sample points along the arc and check each - num_samples = max(8, int(abs(angle_sweep) * radius / 5)) # At least 8 points, or 1 per 5mm of arc length - - move_type = MoveType.ARC_CCW if g_code == 3 else MoveType.ARC_CW - if move_type == MoveType.ARC_CW: - self.travel_moves += 1 - else: - self.extrude_moves += 1 - self.total_moves += 1 - - # Check arc samples - for n in range(num_samples + 1): - t = n / num_samples - angle = start_angle + (angle_sweep * t if g_code == 3 else -angle_sweep * t) - - sample_x = center_x + radius * math.cos(angle) - sample_y = center_y + radius * math.sin(angle) - sample_z = start_z + (end_z - start_z) * t # Interpolate Z - - # Check this point - sample_pos = Position(sample_x, sample_y, sample_z, e) - violations = self._check_bounds(sample_pos) - - if violations: - distance = self._calculate_distance_out(sample_pos) - self.violations.append(Violation( - line_num=line_num, - line_content=line, - position=sample_pos, - move_type=move_type, - violation_types=violations, - distance_out=distance - )) - break # Only record first violation on this arc - - # Update current position to arc end - self.current_pos.x = end_x - self.current_pos.y = end_y - self.current_pos.z = end_z - self.current_pos.e = e - - def _classify_move(self, code: str, old_pos: Position, new_pos: Position) -> MoveType: - if code.startswith('G0'): - return MoveType.TRAVEL - if code.startswith('G1'): - if abs(new_pos.e - old_pos.e) > 0.001: - return MoveType.EXTRUDE - else: - return MoveType.TRAVEL - if code.startswith('G2') or code.startswith('G3'): - return MoveType.EXTRUDE - return MoveType.UNKNOWN - - def _check_bounds(self, pos: Position) -> List[ViolationType]: - violations = [] - epsilon = 0.01 - - if self.bed_type == BedType.RECTANGLE: - if pos.x < self.bed_min[0] - epsilon: - violations.append(ViolationType.X_MIN) - if pos.x > self.bed_max[0] + epsilon: - violations.append(ViolationType.X_MAX) - if pos.y < self.bed_min[1] - epsilon: - violations.append(ViolationType.Y_MIN) - if pos.y > self.bed_max[1] + epsilon: - violations.append(ViolationType.Y_MAX) - elif self.bed_type == BedType.CIRCLE: - dist = math.sqrt((pos.x - self.center[0])**2 + (pos.y - self.center[1])**2) - if dist > self.radius + epsilon: - violations.append(ViolationType.RADIUS) - - if self.max_z > 0 and pos.z > self.max_z + epsilon: - violations.append(ViolationType.Z_MAX) - - return violations - - def _calculate_distance_out(self, pos: Position) -> float: - if self.bed_type == BedType.RECTANGLE: - dx = max(0, self.bed_min[0] - pos.x, pos.x - self.bed_max[0]) - dy = max(0, self.bed_min[1] - pos.y, pos.y - self.bed_max[1]) - dz = max(0, pos.z - self.max_z) if self.max_z > 0 else 0 - return math.sqrt(dx**2 + dy**2 + dz**2) - elif self.bed_type == BedType.CIRCLE: - dist = math.sqrt((pos.x - self.center[0])**2 + (pos.y - self.center[1])**2) - return max(0, dist - self.radius) - return 0.0 - - def get_report(self) -> str: - """生成报告""" - report = [] - report.append("=" * 70) - report.append("G-code边界超限分析报告") - report.append("=" * 70) - report.append("") - - # 床信息 - if self.bed_type == BedType.RECTANGLE: - report.append(f"床类型: 矩形") - report.append(f"床边界: X[{self.bed_min[0]:.1f}, {self.bed_max[0]:.1f}] " - f"Y[{self.bed_min[1]:.1f}, {self.bed_max[1]:.1f}] " - f"Z[0, {self.max_z:.1f}]") - else: - report.append(f"床类型: 圆形") - report.append(f"床中心: ({self.center[0]:.1f}, {self.center[1]:.1f})") - report.append(f"床半径: {self.radius:.1f} mm, Z[0, {self.max_z:.1f}]") - - report.append("") - report.append(f"总行数: {self.total_lines}") - report.append(f"总移动数: {self.total_moves}") - report.append(f" - Travel移动: {self.travel_moves}") - report.append(f" - Extrude移动: {self.extrude_moves}") - report.append("") - - # 超限统计 - report.append(f"发现超限: {len(self.violations)} 处") - - if not self.violations: - report.append("") - report.append("✅ 所有移动都在边界内!") - return "\n".join(report) - - travel_violations = [v for v in self.violations if v.move_type == MoveType.TRAVEL] - extrude_violations = [v for v in self.violations if v.move_type == MoveType.EXTRUDE] - - report.append(f" - Travel超限: {len(travel_violations)}") - report.append(f" - Extrude超限: {len(extrude_violations)}") - report.append("") - - # 超限类型统计 - from collections import Counter - all_vio_types = [] - for v in self.violations: - all_vio_types.extend(v.violation_types) - vio_counter = Counter(all_vio_types) - - report.append("超限类型统计:") - for vio_type, count in vio_counter.most_common(): - report.append(f" {vio_type.value}: {count} 次") - report.append("") - - # 详细列表(前100个) - report.append("=" * 70) - report.append(f"详细超限列表 (前100个):") - report.append("=" * 70) - report.append("") - - for i, violation in enumerate(self.violations[:100], 1): - report.append(f"[{i}] {violation}") - report.append("") - - if len(self.violations) > 100: - report.append(f"... 还有 {len(self.violations) - 100} 个超限未显示") - - return "\n".join(report) - - -class GCodeBoundaryCheckerGUI: - def __init__(self, root): - self.root = root - self.root.title("G-code边界超限检查工具") - self.root.geometry("900x700") - - # 变量 - self.gcode_file = tk.StringVar() - self.bed_type = tk.StringVar(value="rectangle") - self.bed_x = tk.DoubleVar(value=200.0) - self.bed_y = tk.DoubleVar(value=200.0) - self.bed_z = tk.DoubleVar(value=250.0) - self.bed_radius = tk.DoubleVar(value=100.0) - self.origin_x = tk.DoubleVar(value=0.0) - self.origin_y = tk.DoubleVar(value=0.0) - - self.analyzer = None - self.analyzing = False - - self.create_widgets() - - def create_widgets(self): - # 主框架 - main_frame = ttk.Frame(self.root, padding="10") - main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) - - self.root.columnconfigure(0, weight=1) - self.root.rowconfigure(0, weight=1) - main_frame.columnconfigure(0, weight=1) - main_frame.rowconfigure(4, weight=1) - - # 1. 文件选择 - file_frame = ttk.LabelFrame(main_frame, text="1. 选择G-code文件", padding="10") - file_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=5) - file_frame.columnconfigure(1, weight=1) - - ttk.Entry(file_frame, textvariable=self.gcode_file, width=50).grid( - row=0, column=0, sticky=(tk.W, tk.E), padx=5) - ttk.Button(file_frame, text="浏览...", command=self.browse_file).grid( - row=0, column=1, padx=5) - - # 2. 床参数 - bed_frame = ttk.LabelFrame(main_frame, text="2. 床参数配置", padding="10") - bed_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=5) - - # 床类型选择 - type_frame = ttk.Frame(bed_frame) - type_frame.grid(row=0, column=0, columnspan=3, sticky=tk.W, pady=5) - - ttk.Label(type_frame, text="床类型:").pack(side=tk.LEFT, padx=5) - ttk.Radiobutton(type_frame, text="矩形床", variable=self.bed_type, - value="rectangle", command=self.update_bed_type).pack(side=tk.LEFT, padx=5) - ttk.Radiobutton(type_frame, text="圆形床 (Delta)", variable=self.bed_type, - value="circle", command=self.update_bed_type).pack(side=tk.LEFT, padx=5) - - # 矩形床参数 - self.rect_frame = ttk.Frame(bed_frame) - self.rect_frame.grid(row=1, column=0, columnspan=3, sticky=tk.W, pady=5) - - ttk.Label(self.rect_frame, text="尺寸 (mm):").grid(row=0, column=0, padx=5) - ttk.Label(self.rect_frame, text="X:").grid(row=0, column=1) - ttk.Entry(self.rect_frame, textvariable=self.bed_x, width=10).grid(row=0, column=2, padx=2) - ttk.Label(self.rect_frame, text="Y:").grid(row=0, column=3) - ttk.Entry(self.rect_frame, textvariable=self.bed_y, width=10).grid(row=0, column=4, padx=2) - ttk.Label(self.rect_frame, text="Z:").grid(row=0, column=5) - ttk.Entry(self.rect_frame, textvariable=self.bed_z, width=10).grid(row=0, column=6, padx=2) - - ttk.Label(self.rect_frame, text="原点:").grid(row=1, column=0, padx=5, pady=5) - ttk.Label(self.rect_frame, text="X:").grid(row=1, column=1) - ttk.Entry(self.rect_frame, textvariable=self.origin_x, width=10).grid(row=1, column=2, padx=2) - ttk.Label(self.rect_frame, text="Y:").grid(row=1, column=3) - ttk.Entry(self.rect_frame, textvariable=self.origin_y, width=10).grid(row=1, column=4, padx=2) - - # 圆形床参数 - self.circle_frame = ttk.Frame(bed_frame) - self.circle_frame.grid(row=1, column=0, columnspan=3, sticky=tk.W, pady=5) - - ttk.Label(self.circle_frame, text="半径 (mm):").grid(row=0, column=0, padx=5) - ttk.Entry(self.circle_frame, textvariable=self.bed_radius, width=10).grid(row=0, column=1, padx=2) - ttk.Label(self.circle_frame, text="Z高度:").grid(row=0, column=2, padx=5) - ttk.Entry(self.circle_frame, textvariable=self.bed_z, width=10).grid(row=0, column=3, padx=2) - - self.circle_frame.grid_remove() # 初始隐藏 - - # 快速预设 - preset_frame = ttk.Frame(bed_frame) - preset_frame.grid(row=2, column=0, columnspan=3, sticky=tk.W, pady=5) - - ttk.Label(preset_frame, text="快速预设:").pack(side=tk.LEFT, padx=5) - ttk.Button(preset_frame, text="200×200×250", - command=lambda: self.apply_preset(200, 200, 250)).pack(side=tk.LEFT, padx=2) - ttk.Button(preset_frame, text="220×220×250", - command=lambda: self.apply_preset(220, 220, 250)).pack(side=tk.LEFT, padx=2) - ttk.Button(preset_frame, text="250×250×300", - command=lambda: self.apply_preset(250, 250, 300)).pack(side=tk.LEFT, padx=2) - ttk.Button(preset_frame, text="300×300×400", - command=lambda: self.apply_preset(300, 300, 400)).pack(side=tk.LEFT, padx=2) - - # 3. 控制按钮 - control_frame = ttk.Frame(main_frame) - control_frame.grid(row=2, column=0, pady=10) - - self.analyze_btn = ttk.Button(control_frame, text="开始分析", - command=self.start_analysis, width=15) - self.analyze_btn.pack(side=tk.LEFT, padx=5) - - self.save_btn = ttk.Button(control_frame, text="保存报告", - command=self.save_report, width=15, state=tk.DISABLED) - self.save_btn.pack(side=tk.LEFT, padx=5) - - ttk.Button(control_frame, text="清除结果", - command=self.clear_results, width=15).pack(side=tk.LEFT, padx=5) - - # 4. 进度条 - self.progress_var = tk.DoubleVar() - self.progress_label = ttk.Label(main_frame, text="") - self.progress_label.grid(row=3, column=0, sticky=tk.W) - - self.progress_bar = ttk.Progressbar(main_frame, variable=self.progress_var, - maximum=100, mode='determinate') - self.progress_bar.grid(row=3, column=0, sticky=(tk.W, tk.E), pady=5) - - # 5. 结果显示 - result_frame = ttk.LabelFrame(main_frame, text="分析结果", padding="10") - result_frame.grid(row=4, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5) - result_frame.columnconfigure(0, weight=1) - result_frame.rowconfigure(0, weight=1) - - self.result_text = scrolledtext.ScrolledText(result_frame, width=80, height=20, - font=('Courier New', 9)) - self.result_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) - - def browse_file(self): - filename = filedialog.askopenfilename( - title="选择G-code文件", - filetypes=[("G-code文件", "*.gcode *.GCODE *.gco *.GCO"), - ("所有文件", "*.*")] - ) - if filename: - self.gcode_file.set(filename) - - def update_bed_type(self): - if self.bed_type.get() == "rectangle": - self.rect_frame.grid() - self.circle_frame.grid_remove() - else: - self.rect_frame.grid_remove() - self.circle_frame.grid() - - def apply_preset(self, x, y, z): - self.bed_x.set(x) - self.bed_y.set(y) - self.bed_z.set(z) - self.bed_type.set("rectangle") - self.update_bed_type() - - def update_progress(self, progress, current, total): - self.progress_var.set(progress) - self.progress_label.config(text=f"正在分析... {current}/{total} 行 ({progress:.1f}%)") - - def start_analysis(self): - # 验证输入 - if not self.gcode_file.get(): - messagebox.showerror("错误", "请选择G-code文件") - return - - if not Path(self.gcode_file.get()).exists(): - messagebox.showerror("错误", "文件不存在") - return - - # 禁用按钮 - self.analyze_btn.config(state=tk.DISABLED) - self.save_btn.config(state=tk.DISABLED) - self.result_text.delete(1.0, tk.END) - self.progress_var.set(0) - - # 在后台线程中分析 - thread = threading.Thread(target=self.run_analysis) - thread.daemon = True - thread.start() - - def run_analysis(self): - try: - # 准备参数 - if self.bed_type.get() == "rectangle": - bed_type = BedType.RECTANGLE - bed_min = (self.origin_x.get(), self.origin_y.get()) - bed_max = (self.origin_x.get() + self.bed_x.get(), - self.origin_y.get() + self.bed_y.get()) - max_z = self.bed_z.get() - radius = None - else: - bed_type = BedType.CIRCLE - radius = self.bed_radius.get() - bed_min = (-radius, -radius) - bed_max = (radius, radius) - max_z = self.bed_z.get() - - # 创建分析器 - self.analyzer = GCodeAnalyzer(bed_type, bed_min, bed_max, max_z, radius, - progress_callback=self.update_progress) - - # 分析文件 - result = self.analyzer.parse_gcode_file(self.gcode_file.get()) - - if result is not True: - self.root.after(0, lambda: messagebox.showerror("错误", f"分析失败: {result}")) - self.root.after(0, lambda: self.analyze_btn.config(state=tk.NORMAL)) - return - - # 生成报告 - report = self.analyzer.get_report() - - # 更新UI - self.root.after(0, lambda: self.display_results(report)) - - except Exception as e: - self.root.after(0, lambda: messagebox.showerror("错误", f"分析出错: {str(e)}")) - self.root.after(0, lambda: self.analyze_btn.config(state=tk.NORMAL)) - - def display_results(self, report): - self.result_text.delete(1.0, tk.END) - self.result_text.insert(1.0, report) - - # 高亮显示 - if "✅" in report: - self.result_text.tag_config("success", foreground="green", font=('Courier New', 9, 'bold')) - start = self.result_text.search("✅", 1.0, tk.END) - if start: - end = f"{start}+1c" - self.result_text.tag_add("success", start, end) - - self.progress_label.config(text="分析完成!") - self.progress_var.set(100) - self.analyze_btn.config(state=tk.NORMAL) - self.save_btn.config(state=tk.NORMAL) - - # 如果有超限,弹出提示 - if self.analyzer and len(self.analyzer.violations) > 0: - messagebox.showwarning("发现超限", - f"发现 {len(self.analyzer.violations)} 处边界超限!\n" - f"请查看详细报告。") - else: - messagebox.showinfo("检查完成", "✅ 所有移动都在边界内!") - - def save_report(self): - if not self.analyzer: - return - - filename = filedialog.asksaveasfilename( - title="保存报告", - defaultextension=".txt", - filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")], - initialfile="gcode_boundary_report.txt" - ) - - if filename: - try: - with open(filename, 'w', encoding='utf-8') as f: - f.write(self.result_text.get(1.0, tk.END)) - messagebox.showinfo("保存成功", f"报告已保存到:\n{filename}") - except Exception as e: - messagebox.showerror("保存失败", f"保存出错: {str(e)}") - - def clear_results(self): - self.result_text.delete(1.0, tk.END) - self.progress_var.set(0) - self.progress_label.config(text="") - self.analyzer = None - self.save_btn.config(state=tk.DISABLED) - - -def main(): - root = tk.Tk() - app = GCodeBoundaryCheckerGUI(root) - root.mainloop() - - -if __name__ == '__main__': - main() diff --git a/tools/run_gcode_checker.bat b/tools/run_gcode_checker.bat deleted file mode 100644 index 209b0f851c..0000000000 --- a/tools/run_gcode_checker.bat +++ /dev/null @@ -1,28 +0,0 @@ -@echo off -REM G-code边界检查工具 - Windows启动脚本 - -echo ======================================== -echo G-code边界超限检查工具 -echo ======================================== -echo. - -REM 检查Python是否安装 -python --version >nul 2>&1 -if errorlevel 1 ( - echo 错误: 未找到Python - echo 请先安装Python 3.7或更高版本 - echo 下载地址: https://www.python.org/downloads/ - pause - exit /b 1 -) - -echo 启动GUI界面... -echo. - -python "%~dp0gcode_boundary_checker_gui.py" - -if errorlevel 1 ( - echo. - echo 程序运行出错,按任意键退出... - pause -)