diff --git a/.gitignore b/.gitignore index f1a167c..c828440 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,5 @@ tmp/* venv instance huey_queue.* -prusaslicer/* +*.AppImage frpc/* \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3868e50 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# AIO 3D Print Web Platform + +简介 +---- + +这是一个基于 Python 的 Web 打印管理平台,通过调用 OctoPrint 的 API 来控制支持 Klipper 的打印机,集成切片、文件管理和打印机操作等功能。前端资源位于 `app/assets`,包含若干界面截图与帮助文档(见下方示例图片)。 + +示例图片 +--------- + +![Logo](app/assets/img/logo.jpg) + +切片助手示例: + +![Slice Helper](app/assets/img/slice_helper/slice-helper_zh-cn.png) + +快速开始 +-------- + +1. 克隆仓库: + +```bash +git clone https://gitea.lhye.work/lhye200/AIO_3D_Print_Web_Platform.git +cd AIO_3D_Print_Exp +``` + +2. 运行安装脚本(会创建虚拟环境并安装 Python 依赖,下载运行需要的文件,安装 systemd 服务): + +```bash +./install.sh +``` + +安装脚本说明 +------------- + +- 安装脚本会创建 `venv`、安装 `requirements.txt` 中列出的依赖,并尝试设置 systemd 服务。 +- 安装脚本可**可选**下载 PrusaSlicer 的 AppImage 二进制(用于本地进行切片): + - 二进制来源: https://github.com/davidk/PrusaSlicer-ARM.AppImage + - 源码: https://github.com/prusa3d/PrusaSlicer + - 控制方式(环境变量): + - `PRUSA_SKIP_DOWNLOAD=1` : 跳过下载二进制(默认会询问) + - `PRUSA_AGPL_ACCEPT=1` : 自动同意 AGPLv3 条款并下载(默认需要交互确认) + +支持的切片引擎 +--------------- +- `Cura` 有一定支持,但由于其配置方式复杂容易出错,现使用体验不佳。 +- `PrusaSlicer` 较为全面的支持。 + +许可与第三方 +--------------- + +- 本仓库根目录的 `LICENSE` 为本项目主体采用的许可证(GPLv3)。 +- 本项目可选使用的第三方软件 PrusaSlicer 受 AGPLv3 约束;相关说明与合规提示见 [third_party/PRUSASLICER.md](third_party/PRUSASLICER.md)。 +- 如果你在服务器上运行并通过网络提供基于 AGPL 组件的服务,AGPL 可能要求你向使用该服务的用户公开对应源码。 + +AI 协助声明 +---------------- + +本仓库的部分内容由 AI 生成。 + +更多信息 +------------ + +- 代码结构与前端资源位于 `app/`,包括 `app/assets`(图片、脚本、样式)与 `app/templates`。 +- 请阅读 `install.sh` 以了解安装过程的详细步骤与可配置选项。 diff --git a/app/__init__.py b/app/__init__.py index f9e1670..8026682 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -56,8 +56,10 @@ def create_app(): app.config['REMEMBER_COOKIE_NAME'] = 'aio_remember' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///../instance/aio_3d.db' app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {'connect_args': {'timeout': 15}} - app.config['UPLOAD_FOLDER'] = os.path.abspath(os.path.join(app.root_path, '..', 'uploads')) - app.config['PRINT_CONFIG_FOLDER'] = os.path.abspath(os.path.join(app.root_path, '..', 'print_config')) + app.config['UPLOAD_FOLDER'] = os.environ.get('UPLOAD_FOLDER', os.path.abspath(os.path.join(app.root_path, '..', 'uploads'))) + app.config['PRINT_CONFIG_FOLDER'] = os.environ.get('PRINT_CONFIG_FOLDER', os.path.abspath(os.path.join(app.root_path, '..', 'print_config'))) + app.config['PRUSA_SLICE_BIN'] = os.environ.get('PRUSA_SLICE_BIN', os.path.abspath(os.path.join(app.root_path, '..', 'prusaslicer', 'PrusaSlicer-2.9.4-aarch64-full.AppImage'))) + os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) diff --git a/app/assets/doc/printer_helper_de.md b/app/assets/doc/printer_helper_de.md new file mode 100644 index 0000000..24afe50 --- /dev/null +++ b/app/assets/doc/printer_helper_de.md @@ -0,0 +1,58 @@ +# Drucker-Helfer — Kurzanleitung + +## Inhaltsverzeichnis +- Druckerstatus +- Druck vorbereiten +- Steuerung +- Drucker-Helfer (diese Seite) +- Systemkonfiguration (Admin) +- OctoPrint-Panel (Admin) + +--- + +## Druckerstatus + +Zeigt aktuellen Druckerzustand, Temperaturen und aktive Aufgaben an. + +![Druckerstatus Platzhalterbild](assets/doc/images/printer_status_de.png) + +--- + +## Druck vorbereiten + +GCode an den Drucker senden, Temperaturen setzen und mit `Druck vorbereiten` bzw. `Jetzt drucken` starten. + +![Druck vorbereiten Platzhalterbild](assets/doc/images/printer_prepare_de.png) + +--- + +## Steuerung + +Manuelle Grundsteuerungen: Achsen homing, Düsen/Betten bewegen, Pause/Fortsetzen, Druck abbrechen. + +![Steuerung Platzhalterbild](assets/doc/images/printer_control_de.png) + +--- + +## Drucker-Helfer (diese Seite) + +Tipps zur Fehlerbehebung (Netzwerk, Filament, Bettleveling) und Checkliste vor dem Drucken. + +![Drucker Helfer Platzhalterbild](assets/doc/images/printer_helper_de.png) + +--- + +## Systemkonfiguration (Admin) + +Admin-Einstellungen für Druckerabmessungen, Limits, Basisprofile und Verbindungsdaten. + +![Systemkonfiguration Platzhalterbild](assets/doc/images/printer_system_de.png) + +--- + +## OctoPrint-Panel (Admin) + +Eingebettetes OctoPrint-Panel: `OctoPrint Basis-URL` und API-Key konfigurieren, Live-Panel verwenden. + +![OctoPrint Platzhalterbild](assets/doc/images/printer_octoprint_de.png) + diff --git a/app/assets/doc/printer_helper_en.md b/app/assets/doc/printer_helper_en.md new file mode 100644 index 0000000..a26402c --- /dev/null +++ b/app/assets/doc/printer_helper_en.md @@ -0,0 +1,64 @@ +# Printer Helper — Quick Guide + +## Table of Contents +- Printer Status +- Prepare Print +- Control +- Printer Helper (this page) +- System Configuration (Admin) +- OctoPrint Panel (Admin) + +--- + +## Printer Status + +Shows current printer state, temperatures and active job information. + +![Printer Status placeholder image](assets/doc/images/printer_status_en.png) + +Use this page to monitor `Printer Status` and `Active Print Job`. + +--- + +## Prepare Print + +Send prepared GCode to the printer, set temperatures and start a print using `Prepare Print` and `Print Now`. + +![Prepare Print placeholder image](assets/doc/images/printer_prepare_en.png) + +--- + +## Control + +Basic manual controls: home axes, move nozzle/bed, pause/resume and cancel print. + +![Control placeholder image](assets/doc/images/printer_control_en.png) + +--- + +## Printer Helper (this page) + +Guides common troubleshooting steps (connectivity, filament, bed leveling) and quick checks before printing. + +![Printer Helper placeholder image](assets/doc/images/printer_helper_en.png) + +--- + +## System Configuration (Admin) + +Admin-only settings for printer dimensions, limits, shared profiles and connection settings. + +![System Config placeholder image](assets/doc/images/printer_system_en.png) + +--- + +## OctoPrint Panel (Admin) + +Embedded OctoPrint panel: configure `OctoPrint Base URL`, API key and use the live panel when available. + +![OctoPrint Panel placeholder image](assets/doc/images/printer_octoprint_en.png) + +--- + +If you want I can add annotated screenshots for specific printer models. + diff --git a/app/assets/doc/printer_helper_zh-cn.md b/app/assets/doc/printer_helper_zh-cn.md new file mode 100644 index 0000000..fdc39d7 --- /dev/null +++ b/app/assets/doc/printer_helper_zh-cn.md @@ -0,0 +1,60 @@ +# 打印助手 — 快速指南 + +## 目录 +- 打印机状态 +- 准备打印 +- 控制 +- 打印助手(本页) +- 系统配置(管理员) +- OctoPrint 面板(管理员) + +--- + +## 打印机状态 + +显示当前打印机状态、温度和任务信息。 + +![打印机状态 占位图](assets/doc/images/printer_status_zh-cn.png) + +可在此查看 `打印机状态` 与 `当前打印任务`。 + +--- + +## 准备打印 + +将准备好的 GCode 发送到打印机,设置温度并使用 `准备打印` 或 `立即打印` 开始。 + +![准备打印 占位图](assets/doc/images/printer_prepare_zh-cn.png) + +--- + +## 控制 + +手动控制:回原点、移动喷嘴/平台、暂停/恢复与取消打印。 + +![控制 占位图](assets/doc/images/printer_control_zh-cn.png) + +--- + +## 打印助手(本页) + +提供常见故障排查步骤(网络、挤出机、床平整)和打印前检查清单。 + +![打印助手 占位图](assets/doc/images/printer_helper_zh-cn.png) + +--- + +## 系统配置(管理员) + +管理员设置打印机尺寸、限制、基础配置和连接信息。 + +![系统配置 占位图](assets/doc/images/printer_system_zh-cn.png) + +--- + +## OctoPrint 面板(管理员) + +内嵌 OctoPrint 面板:配置 `OctoPrint 基础 URL`、API 密钥并使用可用的实时面板。 + +![OctoPrint 占位图](assets/doc/images/printer_octoprint_zh-cn.png) + diff --git a/app/assets/doc/slice_helper_de.md b/app/assets/doc/slice_helper_de.md new file mode 100644 index 0000000..678a5ac --- /dev/null +++ b/app/assets/doc/slice_helper_de.md @@ -0,0 +1,82 @@ +# Slice-Helfer — Kurzanleitung + +## Inhaltsverzeichnis +- Startseite +- Meine Dateien +- Plater (Bauteilplatte) +- Konto-Verwaltung +- Slice-Helfer (diese Seite) +- Systemeinstellungen (Admin) +- Benutzerverwaltung (Admin) +- API-Schlüssel (Admin) + +--- + +## Startseite + +Übersicht des Slicer-Dashboards und schnelle Aktionen. + +![Startseite Bild](../../assets/img/slice_helper/home_de.png) + +Benutzen Sie die Navigation, um `Startseite` zu öffnen und über `STL Hochladen & Slicen` einen neuen Slice zu starten. + +--- + +## Meine Dateien + +Verwalten Sie hochgeladene STL- und GCode-Dateien: hochladen, herunterladen, löschen. + +![Meine Dateien Bild](../../assets/img/slice_helper/my-files_de.png) + +Wichtige Aktionen: `STL hochladen`, `GCode Herunterladen`, `Löschen`. + +--- + +## Plater (Bauteilplatte) + +Modelle auf der Bauteilplatte anordnen, verschieben, drehen und skalieren. Vor dem Slicen `Zusammenführen & Slicen`. + +![Plater Bild](../../assets/img/slice_helper/plater_de.png) + +--- + +## Konto-Verwaltung + +Für angemeldete Benutzer: Profil, Passwort ändern und aktive Sitzungen verwalten. + +![Konto Bild](../../assets/img/slice_helper/account-management_de.png) + +--- + +## Slice-Helfer (diese Seite) + +Erklärung der empfohlenen Slicing-Schritte: `Qualitätsprofil` wählen, `Support` und `Fülldichte` konfigurieren, dann `Hochladen & Slicen`. + +![Slice Helfer Bild](../../assets/img/slice_helper/slice-helper_de.png) + +Statusmeldungen: `Wartend`, `Slicen`, `Gesliced`, `Fehlgeschlagen`. + +--- + +## Systemeinstellungen (Admin) + +Admins konfigurieren globale Slicer-Engines und Standardprofile. + +![Systemeinstellungen Bild](../../assets/img/slice_helper/system-settings_de.png) + +--- + +## Benutzerverwaltung (Admin) + +Admins können Benutzer hinzufügen/ändern und Quoten sowie Rollen setzen (`Benutzer`, `Admin`). + +![Benutzerverwaltung Bild](../../assets/img/slice_helper/user-management_de.png) + +--- + +## API-Schlüssel (Admin) + +Verwalten Sie API-Schlüssel für externe Integrationen: `Neuen API-Schlüssel erstellen` und `Schlüssel generieren`. + +![API Schlüssel Bild](../../assets/img/slice_helper/api-keys_de.png) + diff --git a/app/assets/doc/slice_helper_en.md b/app/assets/doc/slice_helper_en.md new file mode 100644 index 0000000..e0628b4 --- /dev/null +++ b/app/assets/doc/slice_helper_en.md @@ -0,0 +1,88 @@ +# Slice Helper — Quick Guide + +## Table of Contents +- Home +- My Files +- Plater (Build Plate) +- Account Management +- Slice Helper (this page) +- System Settings (Admin) +- User Management (Admin) +- API Keys (Admin) + +--- + +## Home + +Overview of the slicer dashboard and quick actions. + +![Home image](../../assets/img/slice_helper/home_en.png) + +Use the top navigation to open `Home` and start a new slice via `Upload & Slice STL`. + +--- + +## My Files + +Manage uploaded STL and GCode files. You can upload, delete and download sliced GCode. + +![My Files image](../../assets/img/slice_helper/my-files_en.png) + +Common actions: `Upload STL`, `Download GCode`, `Delete`. + +--- + +## Plater (Build Plate) + +Arrange models on the build plate before slicing. Use translate/rotate/scale tools and `Merge & Slice`. + +![Plater image](../../assets/img/slice_helper/plater_en.png) + +Tip: ensure all models fit the printable area before slicing. + +--- + +## Account Management + +Available when logged in. Update profile, change password, and manage active sessions. + +![Account image](../../assets/img/slice_helper/account-management_en.png) + +--- + +## Slice Helper (this page) + +This page explains slice workflows and recommended settings. Choose a `Quality Profile`, set `Support` and `Infill Density` then `Upload & Slice`. + +![Slice Helper image](../../assets/img/slice_helper/slice-helper_en.png) + +Status messages: `Waiting`, `Slicing`, `Sliced`, `Failed`. + +--- + +## System Settings (Admin) + +Admins can configure global slicer engines and default profiles under `System Settings`. + +![System Settings image](../../assets/img/slice_helper/system-settings_en.png) + +--- + +## User Management (Admin) + +Admins can add/edit users, set quotas and roles (`User`, `Admin`). + +![User Management image](../../assets/img/slice_helper/user-management_en.png) + +--- + +## API Keys (Admin) + +Manage API keys used by external tools. `Create New API Key`, name it and `Generate Key`. + +![API Keys image](../../assets/img/slice_helper/api-keys_en.png) + +--- + +If you need example workflows or screenshots, tell me which page to expand. + diff --git a/app/assets/doc/slice_helper_zh-cn.md b/app/assets/doc/slice_helper_zh-cn.md new file mode 100644 index 0000000..8f09517 --- /dev/null +++ b/app/assets/doc/slice_helper_zh-cn.md @@ -0,0 +1,84 @@ +# 切片助手 — 快速指南 + +## 目录 +- 主页 +- 我的文件 +- 构建板 (Plater) +- 账号管理 +- 切片助手(本页) +- 系统设置(管理员) +- 用户管理(管理员) +- API 密钥(管理员) + +--- + +## 主页 + +切片仪表盘概览与快速操作入口。 + +![主页 图片](../../assets/img/slice_helper/home_zh-cn.png) + +使用导航栏进入“主页”,通过 `上传并切片 STL` 开始新切片。 + +--- + +## 我的文件 + +管理已上传的 STL 与 GCode,可上传、下载或删除文件。 + +![我的文件 图片](../../assets/img/slice_helper/my-files_zh-cn.png) + +常用操作:`上传STL`、`下载 GCode`、`删除`。 + +--- + +## 构建板 (Plater) + +在构建板上放置与调整模型(平移/旋转/缩放),确认位置后使用 `合并并切片`。 + +![构建板 图片](../../assets/img/slice_helper/plater_zh-cn.png) + +提示:切片前确保模型均在可打印范围内。 + +--- + +## 账号管理 + +登录用户可在此更新资料、修改密码并管理活跃会话。 + +![账号管理 图片](../../assets/img/slice_helper/account-management_zh-cn.png) + +--- + +## 切片助手(本页) + +本页说明推荐的切片流程与设置:选择 `质量配置`、设置 `支撑` 与 `填充密度`,然后 `上传 & 切片`。 + +![切片助手 图片](../../assets/img/slice_helper/slice-helper_zh-cn.png) + +状态提示:`等待中`、`切片中`、`已切片`、`失败`。 + +--- + +## 系统设置(管理员) + +管理员可在此配置全局切片引擎与默认配置文件。 + +![系统设置 图片](../../assets/img/slice_helper/system-settings_zh-cn.png) + +--- + +## 用户管理(管理员) + +管理员可添加/编辑用户并设置配额与角色(`普通用户`、`管理员`)。 + +![用户管理 图片](../../assets/img/slice_helper/user-management_zh-cn.png) + +--- + +## API 密钥(管理员) + +管理外部工具使用的 API 密钥;点击 `创建新的 API 密钥`,输入名称并 `生成密钥`。 + +![API 密钥 图片](../../assets/img/slice_helper/api-keys_zh-cn.png) + diff --git a/app/assets/i18n/de.json b/app/assets/i18n/de.json index 8af1888..3a60092 100644 --- a/app/assets/i18n/de.json +++ b/app/assets/i18n/de.json @@ -269,5 +269,13 @@ "Are you sure you want to delete this API Key?": "Sind Sie sicher, dass Sie diesen API-Schlüssel löschen möchten?", "API Key Name": "API-Schlüsselname", "No API keys found.": "Keine API-Schlüssel gefunden.", - "API Keys": "API-Schlüssel" + "API Keys": "API-Schlüssel", + "Slice Helper": "Slice-Helfer", + "Printer Helper": "Drucker-Helfer", + "For security reasons, please change your default admin password.": "Aus Sicherheitsgründen ändern Sie bitte Ihr Standard-Administratorpasswort.", + "Your new password cannot be the default \"admin123\".": "Ihr neues Passwort darf nicht das Standardpasswort \"admin123\" sein.", + "Current password is incorrect.": "Das aktuelle Passwort ist falsch.", + "New passwords do not match.": "Die neuen Passwörter stimmen nicht überein.", + "New password must be at least 6 characters.": "Das neue Passwort muss mindestens 6 Zeichen lang sein.", + "Password updated successfully.": "Passwort erfolgreich aktualisiert." } \ No newline at end of file diff --git a/app/assets/i18n/en.json b/app/assets/i18n/en.json index 886c6eb..77e5153 100644 --- a/app/assets/i18n/en.json +++ b/app/assets/i18n/en.json @@ -269,5 +269,13 @@ "Are you sure you want to delete this API Key?": "Are you sure you want to delete this API Key?", "API Key Name": "API Key Name", "No API keys found.": "No API keys found.", - "API Keys": "API Keys" + "API Keys": "API Keys", + "Slice Helper": "Slice Helper", + "Printer Helper": "Printer Helper", + "For security reasons, please change your default admin password.": "For security reasons, please change your default admin password.", + "Your new password cannot be the default \"admin123\".": "Your new password cannot be the default \"admin123\".", + "Current password is incorrect.": "Current password is incorrect.", + "New passwords do not match.": "New passwords do not match.", + "New password must be at least 6 characters.": "New password must be at least 6 characters.", + "Password updated successfully.": "Password updated successfully." } \ No newline at end of file diff --git a/app/assets/i18n/zh-cn.json b/app/assets/i18n/zh-cn.json index e7888d1..7c1b8ea 100644 --- a/app/assets/i18n/zh-cn.json +++ b/app/assets/i18n/zh-cn.json @@ -269,5 +269,13 @@ "Are you sure you want to delete this API Key?": "您确定要删除此 API 密钥吗?", "API Key Name": "API 密钥名称", "No API keys found.": "未找到 API 密钥。", - "API Keys": "API 密钥" + "API Keys": "API 密钥", + "Slice Helper": "切片助手", + "Printer Helper": "打印助手", + "For security reasons, please change your default admin password.": "出于安全原因,请修改您的默认管理员密码。", + "Your new password cannot be the default \"admin123\".": "新密码不能设置为系统默认的 \"admin123\"。", + "Current password is incorrect.": "当前密码不正确。", + "New passwords do not match.": "新密码不匹配。", + "New password must be at least 6 characters.": "新密码必须至少6个字符。", + "Password updated successfully.": "密码更新成功。" } \ No newline at end of file diff --git a/app/assets/img/favicon.ico b/app/assets/img/favicon.ico new file mode 100644 index 0000000..a06482f Binary files /dev/null and b/app/assets/img/favicon.ico differ diff --git a/app/assets/img/favicon.jpg b/app/assets/img/favicon.jpg new file mode 100644 index 0000000..f4ef7cd Binary files /dev/null and b/app/assets/img/favicon.jpg differ diff --git a/app/assets/img/logo.jpg b/app/assets/img/logo.jpg new file mode 100644 index 0000000..92ef0cf Binary files /dev/null and b/app/assets/img/logo.jpg differ diff --git a/app/assets/img/slice_helper/account-management_de.png b/app/assets/img/slice_helper/account-management_de.png new file mode 100644 index 0000000..d9d395e Binary files /dev/null and b/app/assets/img/slice_helper/account-management_de.png differ diff --git a/app/assets/img/slice_helper/account-management_en.png b/app/assets/img/slice_helper/account-management_en.png new file mode 100644 index 0000000..9f08f29 Binary files /dev/null and b/app/assets/img/slice_helper/account-management_en.png differ diff --git a/app/assets/img/slice_helper/account-management_zh-cn.png b/app/assets/img/slice_helper/account-management_zh-cn.png new file mode 100644 index 0000000..3c86d1a Binary files /dev/null and b/app/assets/img/slice_helper/account-management_zh-cn.png differ diff --git a/app/assets/img/slice_helper/api-keys_de.png b/app/assets/img/slice_helper/api-keys_de.png new file mode 100644 index 0000000..81afd08 Binary files /dev/null and b/app/assets/img/slice_helper/api-keys_de.png differ diff --git a/app/assets/img/slice_helper/api-keys_en.png b/app/assets/img/slice_helper/api-keys_en.png new file mode 100644 index 0000000..de7a14a Binary files /dev/null and b/app/assets/img/slice_helper/api-keys_en.png differ diff --git a/app/assets/img/slice_helper/api-keys_zh-cn.png b/app/assets/img/slice_helper/api-keys_zh-cn.png new file mode 100644 index 0000000..2d7e74d Binary files /dev/null and b/app/assets/img/slice_helper/api-keys_zh-cn.png differ diff --git a/app/assets/img/slice_helper/home_de.png b/app/assets/img/slice_helper/home_de.png new file mode 100644 index 0000000..407d73b Binary files /dev/null and b/app/assets/img/slice_helper/home_de.png differ diff --git a/app/assets/img/slice_helper/home_en.png b/app/assets/img/slice_helper/home_en.png new file mode 100644 index 0000000..422b0e5 Binary files /dev/null and b/app/assets/img/slice_helper/home_en.png differ diff --git a/app/assets/img/slice_helper/home_zh-cn.png b/app/assets/img/slice_helper/home_zh-cn.png new file mode 100644 index 0000000..108069b Binary files /dev/null and b/app/assets/img/slice_helper/home_zh-cn.png differ diff --git a/app/assets/img/slice_helper/my-files_de.png b/app/assets/img/slice_helper/my-files_de.png new file mode 100644 index 0000000..f3285a7 Binary files /dev/null and b/app/assets/img/slice_helper/my-files_de.png differ diff --git a/app/assets/img/slice_helper/my-files_en.png b/app/assets/img/slice_helper/my-files_en.png new file mode 100644 index 0000000..d7d2b45 Binary files /dev/null and b/app/assets/img/slice_helper/my-files_en.png differ diff --git a/app/assets/img/slice_helper/my-files_zh-cn.png b/app/assets/img/slice_helper/my-files_zh-cn.png new file mode 100644 index 0000000..cdeb6be Binary files /dev/null and b/app/assets/img/slice_helper/my-files_zh-cn.png differ diff --git a/app/assets/img/slice_helper/plater_de.png b/app/assets/img/slice_helper/plater_de.png new file mode 100644 index 0000000..c97b3eb Binary files /dev/null and b/app/assets/img/slice_helper/plater_de.png differ diff --git a/app/assets/img/slice_helper/plater_en.png b/app/assets/img/slice_helper/plater_en.png new file mode 100644 index 0000000..abf8d15 Binary files /dev/null and b/app/assets/img/slice_helper/plater_en.png differ diff --git a/app/assets/img/slice_helper/plater_zh-cn.png b/app/assets/img/slice_helper/plater_zh-cn.png new file mode 100644 index 0000000..e5ddc82 Binary files /dev/null and b/app/assets/img/slice_helper/plater_zh-cn.png differ diff --git a/app/assets/img/slice_helper/slice-helper_de.png b/app/assets/img/slice_helper/slice-helper_de.png new file mode 100644 index 0000000..bb205ba Binary files /dev/null and b/app/assets/img/slice_helper/slice-helper_de.png differ diff --git a/app/assets/img/slice_helper/slice-helper_en.png b/app/assets/img/slice_helper/slice-helper_en.png new file mode 100644 index 0000000..48a9407 Binary files /dev/null and b/app/assets/img/slice_helper/slice-helper_en.png differ diff --git a/app/assets/img/slice_helper/slice-helper_zh-cn.png b/app/assets/img/slice_helper/slice-helper_zh-cn.png new file mode 100644 index 0000000..5895a98 Binary files /dev/null and b/app/assets/img/slice_helper/slice-helper_zh-cn.png differ diff --git a/app/assets/img/slice_helper/system-settings_de.png b/app/assets/img/slice_helper/system-settings_de.png new file mode 100644 index 0000000..ba12926 Binary files /dev/null and b/app/assets/img/slice_helper/system-settings_de.png differ diff --git a/app/assets/img/slice_helper/system-settings_en.png b/app/assets/img/slice_helper/system-settings_en.png new file mode 100644 index 0000000..4eae0e5 Binary files /dev/null and b/app/assets/img/slice_helper/system-settings_en.png differ diff --git a/app/assets/img/slice_helper/system-settings_zh-cn.png b/app/assets/img/slice_helper/system-settings_zh-cn.png new file mode 100644 index 0000000..32381a9 Binary files /dev/null and b/app/assets/img/slice_helper/system-settings_zh-cn.png differ diff --git a/app/assets/img/slice_helper/user-management_de.png b/app/assets/img/slice_helper/user-management_de.png new file mode 100644 index 0000000..20ad30d Binary files /dev/null and b/app/assets/img/slice_helper/user-management_de.png differ diff --git a/app/assets/img/slice_helper/user-management_en.png b/app/assets/img/slice_helper/user-management_en.png new file mode 100644 index 0000000..17dd2dc Binary files /dev/null and b/app/assets/img/slice_helper/user-management_en.png differ diff --git a/app/assets/img/slice_helper/user-management_zh-cn.png b/app/assets/img/slice_helper/user-management_zh-cn.png new file mode 100644 index 0000000..c085dcf Binary files /dev/null and b/app/assets/img/slice_helper/user-management_zh-cn.png differ diff --git a/app/routes/admin_routes.py b/app/routes/admin_routes.py index ab6b482..7085a8c 100644 --- a/app/routes/admin_routes.py +++ b/app/routes/admin_routes.py @@ -90,12 +90,40 @@ def settings(): def users(): all_users = User.query.order_by(User.created_at.desc()).all() user_quotas = {} + + # Load defaults + def_guest_stl = SystemConfig.query.filter_by(key="default_guest_stl_quota_mb").first() + def_guest_stl_val = def_guest_stl.value if def_guest_stl else '0' + def_guest_gcode = SystemConfig.query.filter_by(key="default_guest_gcode_quota_mb").first() + def_guest_gcode_val = def_guest_gcode.value if def_guest_gcode else '0' + + def_user_stl = SystemConfig.query.filter_by(key="default_user_stl_quota_mb").first() + def_user_stl_val = def_user_stl.value if def_user_stl else '0' + def_user_gcode = SystemConfig.query.filter_by(key="default_user_gcode_quota_mb").first() + def_user_gcode_val = def_user_gcode.value if def_user_gcode else '0' + for u in all_users: + if u.is_admin: + eff_stl = '0' + eff_gcode = '0' + elif u.is_guest: + eff_stl = def_guest_stl_val + eff_gcode = def_guest_gcode_val + else: + eff_stl = def_user_stl_val + eff_gcode = def_user_gcode_val + sq = SystemConfig.query.filter_by(key=f"user_{u.id}_stl_quota_mb").first() gq = SystemConfig.query.filter_by(key=f"user_{u.id}_gcode_quota_mb").first() + + user_stl = sq.value if sq else '0' + user_gcode = gq.value if gq else '0' + user_quotas[u.id] = { - 'stl': sq.value if sq else '0', - 'gcode': gq.value if gq else '0' + 'stl': user_stl, + 'gcode': user_gcode, + 'eff_stl': eff_stl if user_stl == '0' else user_stl, + 'eff_gcode': eff_gcode if user_gcode == '0' else user_gcode, } return render_template('admin/users.html', users=all_users, user_quotas=user_quotas) diff --git a/app/routes/auth_routes.py b/app/routes/auth_routes.py index 3dae020..59ebcb5 100644 --- a/app/routes/auth_routes.py +++ b/app/routes/auth_routes.py @@ -35,6 +35,9 @@ def login(): if user and check_password_hash(user.password_hash, password): + # Clear old password check flag + session.pop('pwd_check_done', None) + session.pop('must_change_password', None) login_user(user, remember=remember) session_token = str(uuid.uuid4()) # 尝试获取反向代理传递的真实 IP diff --git a/app/routes/main_routes.py b/app/routes/main_routes.py index 2094273..98f5e10 100644 --- a/app/routes/main_routes.py +++ b/app/routes/main_routes.py @@ -21,6 +21,8 @@ main_bp = Blueprint('main', __name__) def check_user_session(): if current_user.is_authenticated and not current_user.is_guest: session_token = session.get('user_session_token') + client_ip = request.headers.get('X-Real-IP') or request.remote_addr + if session_token: user_session = UserSession.query.filter_by(session_token=session_token).first() if not user_session or not user_session.is_active: @@ -30,7 +32,36 @@ def check_user_session(): return redirect(url_for('auth.login')) else: user_session.last_active = datetime.utcnow() + user_session.ip_address = client_ip db.session.commit() + else: + # Re-authenticated via remember me, but no session token + new_session_token = str(uuid.uuid4()) + user_session = UserSession( + user_id=current_user.id, + session_token=new_session_token, + ip_address=client_ip, + user_agent=request.user_agent.string, + last_active=datetime.utcnow() + ) + db.session.add(user_session) + db.session.commit() + session['user_session_token'] = new_session_token + + # Check default admin password securely without checking hash on every request + if current_user.is_admin: + if session.get('pwd_check_done') is None: + session['pwd_check_done'] = True + if check_password_hash(current_user.password_hash, 'admin123'): + session['must_change_password'] = True + else: + session.pop('must_change_password', None) + + if session.get('must_change_password'): + if request.endpoint and request.endpoint not in ['main.account', 'auth.logout', 'static']: + flash('For security reasons, please change your default admin password.', 'warning') + return redirect(url_for('main.account')) + @@ -317,7 +348,7 @@ def preview_gcode(file_id): engine_name = SystemConfig.query.filter_by(key='slicer_engine').first() if engine_name: - engine = get_slicer_engine(str(engine_name.value), current_app.config['PRINT_CONFIG_FOLDER']) + engine = get_slicer_engine(str(engine_name.value), current_app.config['PRINT_CONFIG_FOLDER'], current_app.config['PRUSA_SLICE_BIN']) w, h, hd = engine.get_bed_dimensions() configs = {c.key: c.value for c in SystemConfig.query.all()} offset_x = float(configs.get('offset_x', '0.0')) @@ -365,7 +396,7 @@ def plater(): engine_name = SystemConfig.query.filter_by(key='slicer_engine').first() if engine_name: - engine = get_slicer_engine(str(engine_name.value), current_app.config['PRINT_CONFIG_FOLDER']) + engine = get_slicer_engine(str(engine_name.value), current_app.config['PRINT_CONFIG_FOLDER'], current_app.config['PRUSA_SLICE_BIN']) w, h, hd = engine.get_bed_dimensions() print(f"Bed dimensions: {w}x{h}x{hd}") @@ -384,6 +415,27 @@ def plater(): models = [{'id': f.id, 'name': f.original_filename, 'status': f.status, 'url': url_for('main.serve_proxy_file', file_id=f.id), 'transform_matrix': f.transform_matrix} for f in user_files] return render_template('slice/plater.html', w=w, h=h, hd=hd, last_quality=default_quality, last_material=default_material, models=models, offset_x=offset_x, offset_y=offset_y, default_infill=default_infill, default_support=default_support, default_support_pattern=default_support_pattern, quota_exceeded=quota_exceeded, configs=configs) + +import re +import markdown + +@main_bp.route('/helper_slice') +def helper_slice(): + lang = request.cookies.get('lang', 'en') + filepath = os.path.join(current_app.root_path, 'assets', 'doc', f'slice_helper_{lang}.md') + if not os.path.exists(filepath): + filepath = os.path.join(current_app.root_path, 'assets', 'doc', 'slice_helper_en.md') + + content_html = "" + if os.path.exists(filepath): + with open(filepath, 'r', encoding='utf-8') as f: + md_text = f.read() + content_html = markdown.markdown(md_text, extensions=['fenced_code', 'tables']) + # Rewrite relative image links to /assets/doc/ + content_html = re.sub(r'src="(?!http|/)([^"]+)"', r'src="/assets/doc/\1"', content_html) + + return render_template('slice/helper_slice.html', content_html=content_html) + @main_bp.route('/file/') @login_required def serve_file(file_id): @@ -579,7 +631,7 @@ def build_plate_model(): @main_bp.route('/api/engine_options/') @login_required def engine_options(engine_name): - engine = get_slicer_engine(engine_name, current_app.config['PRINT_CONFIG_FOLDER']) + engine = get_slicer_engine(engine_name, current_app.config['PRINT_CONFIG_FOLDER'], current_app.config['PRUSA_SLICE_BIN']) presets = engine.get_quality_presets() patterns = engine.get_support_patterns() materials = engine.get_materials() if hasattr(engine, 'get_materials') else [] @@ -607,9 +659,13 @@ def account(): flash('New passwords do not match.', 'danger') elif len(new_pass) < 6: flash('New password must be at least 6 characters.', 'danger') + elif current_user.is_admin and new_pass == 'admin123': + flash('Your new password cannot be the default "admin123".', 'danger') else: current_user.password_hash = generate_password_hash(new_pass) db.session.commit() + # If they just changed it, clear the must change flag + session.pop('must_change_password', None) flash('Password updated successfully.', 'success') elif action == 'terminate_session': diff --git a/app/routes/printer_routes.py b/app/routes/printer_routes.py index 3e22d49..a0e17ff 100644 --- a/app/routes/printer_routes.py +++ b/app/routes/printer_routes.py @@ -228,6 +228,26 @@ def control(): error = "OctoPrint is not configured." return render_template('printer/control.html', webcam_url=webcam_url, error=error) +import re +import markdown + +@printer_bp.route('/helper_printer') +def helper_printer(): + lang = request.cookies.get('lang', 'en') + filepath = os.path.join(current_app.root_path, 'assets', 'doc', f'printer_helper_{lang}.md') + if not os.path.exists(filepath): + filepath = os.path.join(current_app.root_path, 'assets', 'doc', 'printer_helper_en.md') + + content_html = "" + if os.path.exists(filepath): + with open(filepath, 'r', encoding='utf-8') as f: + md_text = f.read() + content_html = markdown.markdown(md_text, extensions=['fenced_code', 'tables']) + # Rewrite relative image links to /assets/doc/ + content_html = re.sub(r'src="(?!http|/)([^"]+)"', r'src="/assets/doc/\1"', content_html) + + return render_template('printer/helper_printer.html', content_html=content_html) + @printer_bp.route('/api/command', methods=['POST']) @login_required def api_command(): @@ -456,7 +476,9 @@ def octo_proxy(path): class WebSocketResponse(Response): def __call__(self, *args, **kwargs): print("WS Response __call__") - if getattr(ws, 'mode', 'werkzeug') == 'werkzeug': + if getattr(ws, 'mode', 'werkzeug') == 'gunicorn': + raise StopIteration() + elif getattr(ws, 'mode', 'werkzeug') == 'werkzeug': return super().__call__(*args, **kwargs) return [] diff --git a/app/templates/admin/users.html b/app/templates/admin/users.html index 9055da7..d0c1b54 100644 --- a/app/templates/admin/users.html +++ b/app/templates/admin/users.html @@ -35,8 +35,25 @@ {% endif %} - STL: {{ user_quotas[user.id]['stl'] if user_quotas[user.id]['stl'] != '0' else _('Unlimited') }} MB - GCode: {{ user_quotas[user.id]['gcode'] if user_quotas[user.id]['gcode'] != '0' else _('Unlimited') }} MB + {% if user.is_admin %} + STL: {{ _('Unlimited') }} + GCode: {{ _('Unlimited') }} + {% else %} + STL: + {% if user_quotas[user.id]['stl'] == '0' %} + {{ _('Default') }} ({{ user_quotas[user.id]['eff_stl'] if user_quotas[user.id]['eff_stl'] != '0' else _('Unlimited') }} MB) + {% else %} + {{ user_quotas[user.id]['stl'] }} MB + {% endif %} + + GCode: + {% if user_quotas[user.id]['gcode'] == '0' %} + {{ _('Default') }} ({{ user_quotas[user.id]['eff_gcode'] if user_quotas[user.id]['eff_gcode'] != '0' else _('Unlimited') }} MB) + {% else %} + {{ user_quotas[user.id]['gcode'] }} MB + {% endif %} + + {% endif %} {{ user.created_at.strftime('%Y-%m-%d %H:%M') }} @@ -66,11 +83,11 @@ diff --git a/app/templates/base.html b/app/templates/base.html index 04131a3..e101037 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -4,6 +4,7 @@ AIO 3D Slicer + @@ -131,6 +132,11 @@ {{ _('Control') }} + +
+ {% if content_html %} + {{ content_html|safe }} + {% else %} +

{{ _('Documentation not available.') }}

+ {% endif %} +
+ + + +{% endblock %} diff --git a/app/templates/slice/account.html b/app/templates/slice/account.html index 90ab47a..33dc320 100644 --- a/app/templates/slice/account.html +++ b/app/templates/slice/account.html @@ -84,7 +84,7 @@ {{ s.ip_address }} - {{ s.last_active.strftime('%Y-%m-%d %H:%M:%S') }} + {{ s.last_active.strftime('%Y-%m-%d %H:%M:%S') }} {% if s.session_token != current_token %} @@ -111,4 +111,24 @@ + + {% endblock %} diff --git a/app/templates/slice/helper_slice.html b/app/templates/slice/helper_slice.html new file mode 100644 index 0000000..5eda53f --- /dev/null +++ b/app/templates/slice/helper_slice.html @@ -0,0 +1,56 @@ +{% extends 'base.html' %} + +{% block content %} +
+

{{ _('Slice Helper') }}

+
+ +
+ +
+ {% if content_html %} + {{ content_html|safe }} + {% else %} +

{{ _('Documentation not available.') }}

+ {% endif %} +
+
+ + +{% endblock %} diff --git a/app/utils/octoprint_client.py b/app/utils/octoprint_client.py index fde606b..870a4c3 100644 --- a/app/utils/octoprint_client.py +++ b/app/utils/octoprint_client.py @@ -110,6 +110,10 @@ class OctoPrintClient: """Get information about the current print job and progress.""" return self._request("GET", "/api/job") + def get_printer_err_log(self): + """Fetch the printer error log, if available.""" + return self._request("GET", "/api/printer/error") + # ------------------------------------------------------------------------- # Printer Control # ------------------------------------------------------------------------- diff --git a/app/utils/slice_engines/__init__.py b/app/utils/slice_engines/__init__.py index dd0d3d0..045d312 100644 --- a/app/utils/slice_engines/__init__.py +++ b/app/utils/slice_engines/__init__.py @@ -8,7 +8,7 @@ def get_all_engines(): PrusaSlicerEngine() ] -def get_slicer_engine(engine_name="prusa", print_config_folder=None): +def get_slicer_engine(engine_name="prusa", print_config_folder=None, config_slice_bin_path=None): """ Factory function to retrieve the requested slicing engine instance. Valid names: 'cura', 'prusa_slicer' @@ -18,7 +18,7 @@ def get_slicer_engine(engine_name="prusa", print_config_folder=None): if engine_name in ['cura', 'cura_engine', 'curaengine']: return CuraEngine(print_config_folder) elif engine_name in ['prusa', 'prusa_slicer', 'prusaslicer']: - return PrusaSlicerEngine(print_config_folder) + return PrusaSlicerEngine(print_config_folder, config_slice_bin_path) else: # Default fallback - return PrusaSlicerEngine(print_config_folder) + return PrusaSlicerEngine(print_config_folder, config_slice_bin_path) diff --git a/app/utils/slice_engines/cura_engine.py b/app/utils/slice_engines/cura_engine.py index 8006f31..3df4b93 100644 --- a/app/utils/slice_engines/cura_engine.py +++ b/app/utils/slice_engines/cura_engine.py @@ -20,8 +20,6 @@ class CuraEngine: return result.returncode == 0 or b"Usage:" in result.stdout or b"Usage:" in result.stderr except (FileNotFoundError, OSError): return False - self.display_name = "UltiMaker Cura" - self.is_available = self._check_available() def slice(self, app, stl_filepath, gcode_filepath, **kwargs): diff --git a/app/utils/slice_engines/prusa_slicer_engine.py b/app/utils/slice_engines/prusa_slicer_engine.py index f5ca070..63fff8b 100644 --- a/app/utils/slice_engines/prusa_slicer_engine.py +++ b/app/utils/slice_engines/prusa_slicer_engine.py @@ -5,25 +5,19 @@ import uuid import shutil from app.models import SystemConfig -# Default PrusaSlicer AppImage (aarch64) download URL -PRUSA_DOWNLOAD_URL = "https://github.com/davidk/PrusaSlicer-ARM.AppImage/releases/download/version_2.9.4/PrusaSlicer-2.9.4-aarch64-full.AppImage" class PrusaSlicerEngine: - def __init__(self, print_config_folder=None): + def __init__(self, print_config_folder=None, config_slice_bin_path=None): self.name = "prusa_slicer" self.display_name = "PrusaSlicer" + self.config_slice_bin_path = config_slice_bin_path self.is_available = self._check_available() self.print_config_folder = os.path.join(print_config_folder, 'prusa_slicer') if print_config_folder else None def _check_available(self): try: # Prefer explicit environment variable, then PATH, then a bundled AppImage under the repo - prusa_bin = os.environ.get('PRUSA_SLICER_BIN') or shutil.which('prusa-slicer') or shutil.which('prusa-slicer.exe') - if not prusa_bin: - repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) - local_appimage = os.path.join(repo_root, 'prusaslicer', os.path.basename(PRUSA_DOWNLOAD_URL)) - if os.path.isfile(local_appimage) and os.access(local_appimage, os.X_OK): - prusa_bin = local_appimage + prusa_bin = self.config_slice_bin_path or shutil.which('prusa-slicer') or shutil.which('prusa-slicer.exe') if not prusa_bin: return False result = subprocess.run([prusa_bin, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -44,30 +38,7 @@ class PrusaSlicerEngine: Slices via prusa-slicer CLI mapping standard kwargs to PRUSA parameters where possible. """ try: - # Determine prusa-slicer binary location (env -> PATH -> bundled appimage in repo) - repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) - prusa_env = os.environ.get('PRUSA_SLICER_BIN') - candidates = [] - if prusa_env: - candidates.append(prusa_env) - which_bin = shutil.which('prusa-slicer') or shutil.which('prusa-slicer.exe') - if which_bin: - candidates.append(which_bin) - candidates.extend([ - os.path.join(repo_root, 'prusaslicer', 'prusa-slicer'), - os.path.join(repo_root, 'prusaslicer', os.path.basename(PRUSA_DOWNLOAD_URL)), - os.path.join(repo_root, os.path.basename(PRUSA_DOWNLOAD_URL)) - ]) - - prusa_bin = None - for c in candidates: - if c and os.path.isfile(c) and os.access(c, os.X_OK): - prusa_bin = c - break - - if not prusa_bin: - # fallback to plain name so subprocess will try PATH and give a clear error - prusa_bin = 'prusa-slicer' + prusa_bin = self.config_slice_bin_path or shutil.which('prusa-slicer') or shutil.which('prusa-slicer.exe') # Base command command = [prusa_bin, '-g', stl_filepath, '--output', gcode_filepath] diff --git a/install.sh b/install.sh index ef70179..ad961ff 100755 --- a/install.sh +++ b/install.sh @@ -2,28 +2,77 @@ set -euo pipefail REPO_DIR="$(cd "$(dirname "$0")" && pwd)" + +# 安装目录确认(默认使用脚本所在目录) +DEFAULT_INSTALL_DIR="$REPO_DIR" +echo "默认安装目录: $DEFAULT_INSTALL_DIR" +read -r -p "请输入安装目录(回车使用默认): " INSTALL_DIR_INPUT +if [ -z "$INSTALL_DIR_INPUT" ]; then + INSTALL_DIR="$DEFAULT_INSTALL_DIR" +else + INSTALL_DIR="$INSTALL_DIR_INPUT" +fi + +# 创建并解析目标路径为绝对路径 +mkdir -p "$INSTALL_DIR" +INSTALL_DIR="$(cd "$INSTALL_DIR" && pwd)" + +if [ "$INSTALL_DIR" != "$REPO_DIR" ]; then + echo "选择的安装目录 ($INSTALL_DIR) 与脚本所在目录 ($REPO_DIR) 不同。" + if [ -f "$INSTALL_DIR/run_main.sh" ] || [ -f "$INSTALL_DIR/install.sh" ]; then + echo "目标目录已包含仓库文件;将在该目录继续安装。" + REPO_DIR="$INSTALL_DIR" + else + read -r -p "目标目录不包含本仓库。是否将当前仓库复制到 $INSTALL_DIR 并在其下继续安装?输入 'yes' 或 'y' 表示同意(默认 no): " COPY_REPLY + COPY_REPLY="${COPY_REPLY:-no}" + case "${COPY_REPLY,,}" in + y|yes|是|1) + COPY_CONFIRM=1 + ;; + *) + COPY_CONFIRM=0 + ;; + esac + if [ "$COPY_CONFIRM" -eq 1 ]; then + if command -v rsync >/dev/null 2>&1; then + rsync -a --exclude='.git' "$REPO_DIR/" "$INSTALL_DIR/" + else + cp -a "$REPO_DIR/." "$INSTALL_DIR/" + fi + REPO_DIR="$INSTALL_DIR" + echo "仓库已复制到 $REPO_DIR" + else + echo "将继续使用脚本所在目录作为仓库路径:$REPO_DIR" + echo "(注意:systemd 服务和脚本仍将引用 $REPO_DIR)" + fi + fi +fi + +# 依赖于 REPO_DIR 的路径和变量 VENV_DIR="$REPO_DIR/venv" PYTHON_BIN="${PYTHON:-python3}" PRUSA_URL="https://github.com/davidk/PrusaSlicer-ARM.AppImage/releases/download/version_2.9.4/PrusaSlicer-2.9.4-aarch64-full.AppImage" PRUSA_DIR="$REPO_DIR/prusaslicer" PRUSA_FILE="$PRUSA_DIR/$(basename "$PRUSA_URL")" +PRUSA_SKIP_DOWNLOAD="${PRUSA_SKIP_DOWNLOAD:-0}" +PRUSA_AGPL_ACCEPT="${PRUSA_AGPL_ACCEPT:-0}" -echo "Installing AIO_3D_Print_Web_Platform into: $REPO_DIR" -echo "Using python: $PYTHON_BIN" +echo "正在将 AIO_3D_Print_Web_Platform 安装到:$REPO_DIR" +echo "使用的 Python: $PYTHON_BIN" -echo "Stopping services if running (may require sudo)" +echo "如果服务正在运行,将尝试停止它们(可能需要 sudo)" sudo systemctl stop aio-3d-main.service 2>/dev/null || true sudo systemctl stop aio-3d-huey.service 2>/dev/null || true -echo "Creating virtual environment at $VENV_DIR (if missing)" +echo "正在创建虚拟环境(如果不存在):$VENV_DIR" if [ ! -d "$VENV_DIR" ]; then $PYTHON_BIN -m venv "$VENV_DIR" fi -echo "Activating virtualenv and installing Python requirements" +echo "正在激活虚拟环境并安装 Python 依赖" # shellcheck disable=SC1091 source "$VENV_DIR/bin/activate" -# Detect http(s) proxy (respect both lowercase and uppercase env vars) +# 检测 http(s) 代理(支持大小写环境变量) PROXY="" if [ -n "${HTTPS_PROXY:-}" ]; then PROXY="$HTTPS_PROXY" @@ -36,7 +85,7 @@ elif [ -n "${http_proxy:-}" ]; then fi pip_with_proxy() { - # Usage: pip_with_proxy install [args...] + # 用法: pip_with_proxy install [参数...] if [ -n "$PROXY" ] && [ "$1" = "install" ]; then shift pip install --proxy "$PROXY" "$@" @@ -49,43 +98,75 @@ pip_with_proxy install --upgrade pip setuptools wheel if [ -f "$REPO_DIR/requirements.txt" ]; then pip_with_proxy install -r "$REPO_DIR/requirements.txt" else - echo "Warning: requirements.txt not found in $REPO_DIR" + echo "警告:在 $REPO_DIR 未找到 requirements.txt" fi -echo "Ensure run scripts are executable" +echo "确保运行脚本具有可执行权限" chmod +x "$REPO_DIR/run_main.sh" "$REPO_DIR/run_huey.sh" -echo "Checking PrusaSlicer AppImage (optional)" +echo "正在检查 PrusaSlicer AppImage(可选)" mkdir -p "$PRUSA_DIR" if [ ! -f "$PRUSA_FILE" ]; then - echo "Downloading PrusaSlicer AppImage to $PRUSA_FILE" - if command -v curl >/dev/null 2>&1; then - if [ -n "$PROXY" ]; then - curl -x "$PROXY" -L -o "$PRUSA_FILE" "$PRUSA_URL" - else - curl -L -o "$PRUSA_FILE" "$PRUSA_URL" - fi - elif command -v wget >/dev/null 2>&1; then - if [ -n "$PROXY" ]; then - env HTTP_PROXY="$PROXY" HTTPS_PROXY="$PROXY" wget -O "$PRUSA_FILE" "$PRUSA_URL" - else - wget -O "$PRUSA_FILE" "$PRUSA_URL" - fi + if [ "$PRUSA_SKIP_DOWNLOAD" = "1" ]; then + echo "检测到 PRUSA_SKIP_DOWNLOAD=1,跳过 PrusaSlicer 下载。" else - echo "Warning: neither curl nor wget found; cannot download PrusaSlicer AppImage automatically." - fi - if [ -f "$PRUSA_FILE" ]; then - chmod +x "$PRUSA_FILE" + cat <<'AGPL_NOTICE' +PrusaSlicer 使用 GNU Affero General Public License v3 (AGPLv3) 授权。 +源码仓库: https://github.com/prusa3d/PrusaSlicer +本安装器引用的二进制仓库: https://github.com/davidk/PrusaSlicer-ARM.AppImage +下载并运行 PrusaSlicer 即表示您同意 AGPLv3 的许可条款。 +如果您通过网络向用户提供基于该软件的服务,AGPLv3 可能要求您向用户提供相应源码。 +详情请参见 third_party/PRUSASLICER.md 获取源码与合规说明。 +AGPL_NOTICE + + if [ "$PRUSA_AGPL_ACCEPT" != "1" ]; then + read -r -p "是否接受 AGPLv3 许可并允许下载 PrusaSlicer 二进制?输入 'yes' 或 'y' 表示同意(或设置 PRUSA_AGPL_ACCEPT=1 自动同意): " PRUSA_REPLY + else + PRUSA_REPLY="yes" + fi + + case "${PRUSA_REPLY,,}" in + y|yes|是|1) + PRUSA_APPROVED=1 + ;; + *) + PRUSA_APPROVED=0 + ;; + esac + + if [ "$PRUSA_APPROVED" -eq 1 ]; then + echo "正在下载 PrusaSlicer AppImage 到 $PRUSA_FILE" + if command -v curl >/dev/null 2>&1; then + if [ -n "$PROXY" ]; then + curl -x "$PROXY" -L -o "$PRUSA_FILE" "$PRUSA_URL" + else + curl -L -o "$PRUSA_FILE" "$PRUSA_URL" + fi + elif command -v wget >/dev/null 2>&1; then + if [ -n "$PROXY" ]; then + env HTTP_PROXY="$PROXY" HTTPS_PROXY="$PROXY" wget -O "$PRUSA_FILE" "$PRUSA_URL" + else + wget -O "$PRUSA_FILE" "$PRUSA_URL" + fi + else + echo "警告:未检测到 curl 或 wget,无法自动下载 PrusaSlicer AppImage。" + fi + if [ -f "$PRUSA_FILE" ]; then + chmod +x "$PRUSA_FILE" + fi + else + echo "用户未接受 AGPL,已跳过 PrusaSlicer 下载。" + fi fi else - echo "PrusaSlicer AppImage already present: $PRUSA_FILE" + echo "已存在 PrusaSlicer AppImage:$PRUSA_FILE" fi -echo "Prepare and install systemd service files (requires sudo)" +echo "准备并安装 systemd 服务文件(需要 sudo)" for svc in "aio-3d-main.service" "aio-3d-huey.service"; do SRC="$REPO_DIR/$svc" if [ ! -f "$SRC" ]; then - echo "Warning: $SRC not found, skipping" + echo "警告:未找到 $SRC,跳过" continue fi @@ -101,15 +182,15 @@ for svc in "aio-3d-main.service" "aio-3d-huey.service"; do if ($0 ~ /^ExecStart=/) { print "ExecStart=" exec; next } \ print $0 }' "$SRC" > "$TMPFILE" - echo "Installing $svc -> /etc/systemd/system/$svc" + echo "正在安装 $svc -> /etc/systemd/system/$svc" sudo cp "$TMPFILE" "/etc/systemd/system/$svc" done -echo "Reloading systemd daemon and enabling services" +echo "重新加载 systemd 守护进程并启用服务" sudo systemctl daemon-reload sudo systemctl enable aio-3d-main.service aio-3d-huey.service || true sudo systemctl restart aio-3d-huey.service || true sudo systemctl restart aio-3d-main.service || true -echo "Installation completed successfully" +echo "安装完成" diff --git a/prusaslicer/PrusaSlicer/LICENSE b/prusaslicer/PrusaSlicer/LICENSE new file mode 100644 index 0000000..70eec1e --- /dev/null +++ b/prusaslicer/PrusaSlicer/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/requirements.txt b/requirements.txt index 73a8127..2026fb5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ trimesh requests httpx gunicorn>=26.0.0 +Markdown>=3.3.0 # Numeric & STL handling numpy>=1.25.0 @@ -20,4 +21,5 @@ websockets>=11.0.0 # Optional / heavy dependencies for STL simplification (may require system libraries) # pymeshlab # open3d -# pyfqmr \ No newline at end of file +# pyfqmr + diff --git a/run_huey.sh b/run_huey.sh index b0a808f..b79d18a 100755 --- a/run_huey.sh +++ b/run_huey.sh @@ -1,5 +1,11 @@ #!/bin/bash +# You can change these path by environ +# export UPLOAD_FOLDER="$(dirname "$0")/uploads" +# export PRINT_CONFIG_FOLDER="$(dirname "$0")/print_config" +# export PRUSA_SLICE_BIN="$(dirname "$0")/prusaslicer/PrusaSlicer-2.9.4-aarch64-full.AppImage" + + source "$(dirname "$0")/venv/bin/activate" # huey_consumer "run_huey.huey" > /dev/null 2>&1 diff --git a/run_main.sh b/run_main.sh index ab6c5c3..32bf509 100755 --- a/run_main.sh +++ b/run_main.sh @@ -1,9 +1,14 @@ #!/bin/bash +# You can change these path by environ +# export UPLOAD_FOLDER="$(dirname "$0")/uploads" +# export PRINT_CONFIG_FOLDER="$(dirname "$0")/print_config" +# export PRUSA_SLICE_BIN="$(dirname "$0")/prusaslicer/PrusaSlicer-2.9.4-aarch64-full.AppImage" + source "$(dirname "$0")/venv/bin/activate" # python "$(dirname "$0")/run.py" > /dev/null 2>&1 -# python "$(dirname "$0")/run.py" +python "$(dirname "$0")/run.py" -python -c "from run import init_admin; init_admin()" # 确保管理员初始化被执行 -gunicorn -w 4 -b 0.0.0.0:5001 "run:app" +# python -c "from run import init_admin; init_admin()" # 确保管理员初始化被执行 +# gunicorn -k gthread --threads 10 -w 4 -b 0.0.0.0:5001 "run:app" diff --git a/scripts/all_keys.txt b/scripts/all_keys.txt new file mode 100644 index 0000000..57830ba --- /dev/null +++ b/scripts/all_keys.txt @@ -0,0 +1,432 @@ +acceleration_limit_mess +acceleration_limit_mess_enable +accel_to_decel_enable +accel_to_decel_factor +activate_air_filtration +activate_chamber_temp_control +adaptive_layer_height +additional_cooling_fan_speed +after_layer_gcode +ai_infill +alternate_extra_wall +bed_shape +bed_type +before_layer_gcode +bottom_shell_layers +bottom_shell_thickness +bottom_solid_infill_flow_ratio +bottom_surface_pattern +bridge_acceleration +bridge_angle +bridge_density +bridge_flow +bridge_no_support +bridge_speed +brim_ears_detection_length +brim_ears_max_angle +brim_object_gap +brim_type +brim_width +chamber_temperature +close_fan_the_first_x_layers +compatible_printers_condition +complete_print_exhaust_fan_speed +cool_cds_fan_start_at_height +cooling_tube_length +cooling_tube_retraction +cool_plate_temp +cool_plate_temp_initial_layer +cool_special_cds_fan_speed +counterbore_hole_bridging +customized_plate_temp +customized_plate_temp_initial_layer +default_acceleration +default_filament_colour +default_jerk +deretract_speed +detect_narrow_internal_solid_infill +detect_overhang_wall +detect_thin_wall +dont_filter_internal_bridges +draft_shield +during_print_exhaust_fan_speed +elefant_foot_compensation +elefant_foot_compensation_layers +enable_arc_fitting +enable_overhang_bridge_fan +enable_overhang_speed +enable_pressure_advance +enable_prime_tower +enable_special_area_additional_cooling_fan +end_gcode +enforce_support_layers +eng_plate_temp +eng_plate_temp_initial_layer +ensure_vertical_shell_thickness +epoxy_resin_plate_temp +epoxy_resin_plate_temp_initial_layer +exclude_object +extra_loading_move +extra_perimeters_on_overhangs +extruder_clearance_height_to_lid +extruder_clearance_height_to_rod +extruder_clearance_radius +extruder_colour +extruder_offset +family +fan_cooling_layer_time +fan_max_speed +fan_min_speed +filament_cooling_final_speed +filament_cooling_initial_speed +filament_cooling_moves +filament_cost +filament_density +filament_deretraction_speed +filament_diameter +filament_end_gcode +filament_flow_ratio +filament_is_support +filament_loading_speed +filament_loading_speed_start +filament_load_time +filament_max_volumetric_speed +filament_minimal_purge_on_wipe_tower +filament_multitool_ramming +filament_multitool_ramming_flow +filament_multitool_ramming_volume +filament_notes +filament_ramming_parameters +filament_retract_before_wipe +filament_retraction_length +filament_retraction_minimum_travel +filament_retraction_speed +filament_retract_lift_above +filament_retract_lift_below +filament_retract_lift_enforce +filament_retract_restart_extra +filament_retract_when_changing_layer +filament_settings_id +filament_shrink +filament_shrinkage_compensation_z +filament_soluble +filament_start_gcode +filament_toolchange_delay +filament_type +filament_unloading_speed +filament_unloading_speed_start +filament_unload_time +filament_vendor +filament_wipe +filament_wipe_distance +filament_z_hop +filament_z_hop_types +filter_out_gap_fill +first_layer_bed_temperature +flush_into_infill +flush_into_objects +flush_into_support +full_fan_speed_layer +fuzzy_skin +fuzzy_skin_first_layer +fuzzy_skin_point_distance +fuzzy_skin_thickness +gap_fill_target +gap_infill_speed +gcode_add_line_number +gcode_comments +gcode_flavor +gcode_label_objects +high_current_on_filament_swap +hole_to_polyhole +hole_to_polyhole_threshold +hole_to_polyhole_twisted +hot_plate_temp +hot_plate_temp_initial_layer +idle_temperature +independent_support_layer_height +infill_anchor +infill_anchor_max +infill_combination +infill_direction +infill_jerk +infill_wall_overlap +initial_layer_acceleration +initial_layer_infill_speed +initial_layer_jerk +initial_layer_line_width +initial_layer_min_bead_width +initial_layer_print_height +initial_layer_speed +initial_layer_travel_speed +inner_wall_acceleration +inner_wall_jerk +inner_wall_line_width +inner_wall_speed +interface_shells +internal_bridge_flow +internal_bridge_speed +internal_solid_infill_acceleration +internal_solid_infill_line_width +internal_solid_infill_pattern +internal_solid_infill_speed +ironing_angle +ironing_flow +ironing_pattern +ironing_spacing +ironing_speed +ironing_support_layer +ironing_type +is_infill_first +layer_height +line_width +machine_limits_usage +machine_max_acceleration_e +machine_max_acceleration_extruding +machine_max_acceleration_retracting +machine_max_acceleration_travel +machine_max_acceleration_x +machine_max_acceleration_y +machine_max_acceleration_z +machine_max_jerk_e +machine_max_jerk_x +machine_max_jerk_y +machine_max_jerk_z +machine_max_speed_e +machine_max_speed_x +machine_max_speed_y +machine_max_speed_z +machine_min_extruding_rate +machine_min_travel_rate +make_overhang_printable +make_overhang_printable_angle +make_overhang_printable_hole_size +material_flow_dependent_temperature +material_flow_temp_graph +material_type +max_bridge_length +max_layer_height +max_print_height +max_travel_detour_distance +max_volumetric_extrusion_rate_slope +max_volumetric_extrusion_rate_slope_segment_length +min_bead_width +min_feature_size +minimum_sparse_infill_area +minimum_support_area +min_layer_height +min_length_factor +min_width_top_surface +mmu_segmented_region_interlocking_depth +mmu_segmented_region_max_width +nozzle_diameter +nozzle_temperature +nozzle_temperature_initial_layer +nozzle_temperature_range_high +nozzle_temperature_range_low +only_one_wall_first_layer +only_one_wall_top +ooze_prevention +outer_wall_acceleration +outer_wall_jerk +outer_wall_line_width +outer_wall_speed +overhang_1_4_speed +overhang_2_4_speed +overhang_3_4_speed +overhang_4_4_speed +overhang_fan_speed +overhang_fan_threshold +overhang_reverse +overhang_reverse_internal_only +overhang_reverse_threshold +overhang_speed_classic +parking_pos_retraction +pause_print_gcode +precise_outer_wall +pressure_advance +prime_tower_brim_width +prime_tower_enhance_type +prime_tower_width +prime_volume +printer_model +printer_technology +printer_variant +print_flow_ratio +print_order +print_sequence +print_settings_id +raft_contact_distance +raft_expansion +raft_first_layer_density +raft_first_layer_expansion +raft_layers +reduce_crossing_wall +reduce_fan_stop_start_freq +reduce_infill_retraction +required_nozzle_HRC +resolution +retract_before_travel +retract_before_wipe +retract_layer_change +retract_length +retract_length_toolchange +retract_lift_above +retract_lift_below +retract_restart_extra +retract_restart_extra_toolchange +retract_speed +role_based_wipe_speed +scarf_angle_threshold +scarf_joint_flow_ratio +scarf_joint_speed +scarf_overhang_threshold +seam_gap +seam_position +seam_slope_conditional +seam_slope_entire_loop +seam_slope_inner_walls +seam_slope_min_length +seam_slope_start_height +seam_slope_steps +seam_slope_type +show_name +silent_mode +single_extruder_multi_material +single_extruder_multi_material_priming +skirt_distance +skirt_height +skirt_loops +skirt_speed +slice_closing_radius +slicing_mode +slowdown_for_curled_perimeters +slow_down_for_layer_cooling +slow_down_layers +slow_down_layer_time +slow_down_min_speed +small_area_infill_flow_compensation +small_area_infill_flow_compensation_model +small_perimeter_speed +small_perimeter_threshold +solid_infill_filament +sparse_infill_acceleration +sparse_infill_density +sparse_infill_filament +sparse_infill_line_width +sparse_infill_pattern +sparse_infill_speed +speed_limit_to_height +speed_limit_to_height_enable +spiral_mode +spiral_mode_max_xy_smoothing +spiral_mode_smooth +staggered_inner_seams +standby_temperature_delta +start_filament_gcode +start_gcode +support_angle +support_base_pattern +support_base_pattern_spacing +support_bottom_interface_spacing +support_bottom_z_distance +support_critical_regions_only +support_expansion +support_interface_bottom_layers +support_interface_filament +support_interface_loop_pattern +support_interface_not_for_body +support_interface_pattern +support_interface_spacing +support_interface_speed +support_interface_top_layers +support_line_width +support_material +support_material_angle +support_material_auto +support_material_bottom_interface_layers +support_material_extrusion_width +support_material_interface_extruder +support_material_interface_fan_speed +support_material_pattern +support_material_spacing +support_material_speed +support_material_style +support_material_threshold +support_material_xy_spacing +support_object_xy_distance +support_on_build_plate_only +support_remove_small_overhang +support_speed +support_threshold_angle +support_top_z_distance +support_tree_angle +support_tree_angle_slow +support_tree_branch_diameter +support_tree_branch_diameter_angle +support_tree_branch_diameter_double_wall +support_tree_branch_distance +support_tree_tip_diameter +support_tree_top_rate +support_type +support_xy_overrides_z +temperature_vitrification +textured_plate_temp +textured_plate_temp_initial_layer +thick_bridges +thick_internal_bridges +timelapse_type +top_shell_layers +top_shell_thickness +top_solid_infill_flow_ratio +top_surface_acceleration +top_surface_jerk +top_surface_line_width +top_surface_pattern +top_surface_speed +travel_acceleration +travel_jerk +travel_speed +travel_speed_z +tree_support_adaptive_layer_height +tree_support_angle_slow +tree_support_auto_brim +tree_support_branch_angle +tree_support_branch_angle_organic +tree_support_branch_diameter +tree_support_branch_diameter_angle +tree_support_branch_diameter_double_wall +tree_support_branch_diameter_organic +tree_support_branch_distance +tree_support_branch_distance_organic +tree_support_brim_width +tree_support_tip_diameter +tree_support_top_rate +tree_support_wall_count +use_firmware_retraction +use_relative_e_distances +wall_direction +wall_distribution_count +wall_filament +wall_generator +wall_infill_order +wall_loops +wall_sequence +wall_transition_angle +wall_transition_filter_deviation +wall_transition_length +wipe +wipe_before_external_loop +wipe_on_loops +wipe_speed +wipe_tower_bridging +wipe_tower_cone_angle +wipe_tower_extra_spacing +wipe_tower_no_sparse_layers +wipe_tower_rotation_angle +wiping_volumes_extruders +xy_contour_compensation +xy_hole_compensation +z_hop +z_hop_types +z_offset diff --git a/scripts/fix_ini_files.py b/scripts/fix_ini_files.py new file mode 100644 index 0000000..d2dd8d1 --- /dev/null +++ b/scripts/fix_ini_files.py @@ -0,0 +1,54 @@ +import os +import glob +import difflib + +# Read valid keys +valid_keys = set() +with open('valid_keys.txt', 'r') as f: + for line in f: + valid_keys.add(line.strip()) + +def process_file(filepath): + with open(filepath, 'r') as f: + lines = f.readlines() + + new_lines = [] + changed = False + + for line in lines: + stripped = line.strip() + # Skip empty lines, metadata sections, or already commented lines with ; + if not stripped or stripped.startswith('[') or stripped.startswith(';'): + new_lines.append(line) + continue + + if '=' in line: + parts = line.split('=', 1) + key = parts[0].strip() + val = parts[1] + + if key in valid_keys: + new_lines.append(line) + else: + matches = difflib.get_close_matches(key, valid_keys, n=1, cutoff=0.8) + if matches: + new_key = matches[0] + new_lines.append(line.replace(key + ' ', new_key + ' ', 1) if key + ' ' in line else line.replace(key + '=', new_key + '=', 1)) + print(f"{filepath}: Reacted {key} to {new_key}") + changed = True + else: + new_lines.append(';;;' + line) + print(f"{filepath}: Commented {key}") + changed = True + else: + new_lines.append(line) + + if changed: + with open(filepath, 'w') as f: + f.writelines(new_lines) + +for root, dirs, files in os.walk('print_config/prusa_slicer'): + for file in files: + if file.endswith('.ini'): + process_file(os.path.join(root, file)) + diff --git a/scripts/llm_semantic_fix2.py b/scripts/llm_semantic_fix2.py new file mode 100644 index 0000000..0c2f0c8 --- /dev/null +++ b/scripts/llm_semantic_fix2.py @@ -0,0 +1,246 @@ +import os + +def load_valid_keys(): + valid = set() + if os.path.exists('valid_keys.txt'): + with open('valid_keys.txt', 'r') as f: + for line in f: + if line.strip(): + valid.add(line.strip()) + # 补充一些在 PrusaSlicer ini常见但可能在cli中缺失的原生合法字段 + valid.update([ + "start_gcode", "end_gcode", "before_layer_gcode", "temperature", + "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", + "printer_model", "family", "z_offset", "printer_technology", + "gcode_flavor", "silent_mode", "printer_variant", "max_print_height", + "nozzle_diameter", "extruder_colour", "extruder_offset", "use_relative_e_distances", + "use_firmware_retraction", "retract_layer_change", "retract_length", + "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", + "deretract_speed", "retract_before_travel", "retract_before_wipe", "wipe", + "machine_limits_usage", "machine_max_acceleration_x", "machine_max_acceleration_y", + "machine_max_acceleration_z", "machine_max_acceleration_e", + "machine_max_speed_x", "machine_max_speed_y", "machine_max_speed_z", + "machine_max_speed_e", "machine_max_jerk_x", "machine_max_jerk_y", + "machine_max_jerk_z", "machine_max_jerk_e", "machine_min_travel_rate", + "default_filament_colour", "filament_type", "filament_diameter", + "filament_density", "filament_cost", "fan_always_on", "cooling", + "support_material", "support_material_auto", "support_material_style" + ]) + return valid + +VALID_KEYS = load_valid_keys() + +# 基于全量 all_keys.txt 逐一梳理的语义映射字典 +SEMANTIC_MAP = { + # 打印层高相关 + "adaptive_layer_height": "variable_layer_height", + "initial_layer_print_height": "first_layer_height", + "layer_height": "layer_height", + "min_layer_height": "min_layer_height", + "max_layer_height": "max_layer_height", + "print_sequence": "complete_objects", + + # 线宽相关 + "line_width": "extrusion_width", + "initial_layer_line_width": "first_layer_extrusion_width", + "outer_wall_line_width": "external_perimeter_extrusion_width", + "inner_wall_line_width": "perimeter_extrusion_width", + "top_surface_line_width": "top_infill_extrusion_width", + "sparse_infill_line_width": "infill_extrusion_width", + "internal_solid_infill_line_width": "solid_infill_extrusion_width", + + # 墙/外壳相关 + "wall_loops": "perimeters", + "top_shell_layers": "top_solid_layers", + "bottom_shell_layers": "bottom_solid_layers", + "top_shell_thickness": "top_solid_min_thickness", + "bottom_shell_thickness": "bottom_solid_min_thickness", + "only_one_wall_top": "top_one_perimeter_type", + "detect_thin_wall": "thin_walls", + # "detect_narrow_internal_solid_infill": "thin_walls", + "reduce_crossing_wall": "avoid_crossing_perimeters", + + # 填充相关 + "sparse_infill_density": "fill_density", + "sparse_infill_pattern": "fill_pattern", + "infill_direction": "fill_angle", + "infill_wall_overlap": "infill_overlap", + "infill_combination": "solid_infill_every_layers", + "bottom_surface_pattern": "bottom_fill_pattern", + "top_surface_pattern": "top_fill_pattern", + "gap_fill_target": "gap_fill_enabled", + + # 速度相关 + "initial_layer_speed": "first_layer_speed", + "initial_layer_infill_speed": "first_layer_infill_speed", + "outer_wall_speed": "external_perimeter_speed", + "inner_wall_speed": "perimeter_speed", + "top_surface_speed": "top_solid_infill_speed", + "sparse_infill_speed": "infill_speed", + "internal_solid_infill_speed": "solid_infill_speed", + "gap_infill_speed": "gap_fill_speed", + "bridge_speed": "bridge_speed", + "travel_speed": "travel_speed", + "travel_speed_z": "travel_speed_z", + "small_perimeter_speed": "small_perimeter_speed", + "support_speed": "support_material_speed", + "support_interface_speed": "support_material_interface_speed", + + # 加速度相关 + "default_acceleration": "default_acceleration", + "initial_layer_acceleration": "first_layer_acceleration", + "outer_wall_acceleration": "external_perimeter_acceleration", + "inner_wall_acceleration": "perimeter_acceleration", + "top_surface_acceleration": "top_solid_infill_acceleration", + "travel_acceleration": "travel_acceleration", + "bridge_acceleration": "bridge_acceleration", + + # 支撑相关 + "support_angle": "support_material_angle", + "support_top_z_distance": "support_material_contact_distance", + "support_bottom_z_distance": "support_material_bottom_contact_distance", + "support_interface_top_layers": "support_material_interface_layers", + "support_interface_bottom_layers": "support_material_bottom_interface_layers", + "support_interface_spacing": "support_material_interface_spacing", + # "support_remove_small_overhang": "support_material_threshold", + "support_interface_pattern": "support_material_interface_pattern", + + # 底座/附着相关 + "brim_width": "brim_width", + "raft_layers": "raft_layers", + "raft_contact_distance": "raft_contact_distance", + "raft_expansion": "raft_expansion", + "raft_first_layer_density": "raft_first_layer_density", + "raft_first_layer_expansion": "raft_first_layer_expansion", + "skirt_distance": "skirt_distance", + "skirt_height": "skirt_height", + "skirt_loops": "skirts", + "elefant_foot_compensation": "elefant_foot_compensation", + + # 回抽与耗材相关 + "z_hop": "retract_lift", + "retract_length": "retract_length", + "retract_speed": "retract_speed", + "retract_before_wipe": "retract_before_wipe", + "retract_before_travel": "retract_before_travel", + "retract_layer_change": "retract_layer_change", + "retract_lift_above": "retract_lift_above", + "retract_lift_below": "retract_lift_below", + "filament_deretraction_speed": "filament_deretract_speed", + "filament_retraction_length": "filament_retract_length", + "filament_retraction_speed": "filament_retract_speed", + "material_type": "filament_type", + "nozzle_temperature": "temperature", + "nozzle_temperature_initial_layer": "first_layer_temperature", + "filament_flow_ratio": "extrusion_multiplier", + + # 其他属性 + "bridge_flow": "bridge_flow_ratio", + # "idle_temperature": "standby_temperature_delta", + "enable_arc_fitting": "arc_fitting", + "slowdown_for_curled_perimeters": "avoid_crossing_curled_overhangs", + "slow_down_layer_time": "slowdown_below_layer_time", + "fan_max_speed": "max_fan_speed", + "fan_min_speed": "min_fan_speed", + "spiral_mode": "spiral_vase", + "prime_tower_brim_width": "wipe_tower_brim_width", + "prime_tower_width": "wipe_tower_width", + + "bridge_no_support": "dont_support_bridges", + "minimum_sparse_infill_area": "solid_infill_below_area", + "xy_hole_compensation": "xy_size_compensation", + "enable_prime_tower": "wipe_tower", + "ironing_flow": "ironing_flowrate", + "overhang_1_4_speed": "overhang_speed_0", + "overhang_2_4_speed": "overhang_speed_1", + "overhang_3_4_speed": "overhang_speed_2", + "overhang_4_4_speed": "overhang_speed_3", + "enable_overhang_speed": "enable_dynamic_overhang_speeds", + "enforce_support_layers": "support_material_enforce_layers", + "fuzzy_skin_point_distance": "fuzzy_skin_point_dist", + # "initial_layer_min_bead_width": "min_bead_width", + # "internal_bridge_flow": "bridge_flow_ratio", + # "internal_bridge_speed": "bridge_speed", + "internal_solid_infill_acceleration": "solid_infill_acceleration", + "internal_solid_infill_pattern": "solid_fill_pattern", + "is_infill_first": "infill_first", + "seam_gap": "seam_gap_distance", + "seam_slope_entire_loop": "scarf_seam_entire_loop", + "seam_slope_inner_walls": "scarf_seam_on_inner_perimeters", + "seam_slope_min_length": "scarf_seam_length", + "seam_slope_start_height": "scarf_seam_start_height", + "sparse_infill_acceleration": "infill_acceleration", + "internal_solid_infill_acceleration": "solid_infill_acceleration", + "wall_generator": "perimeter_generator", + # "wipe_tower_rotation_angle": "wipe_tower_cone_angle" +} + +def process_file(filepath): + with open(filepath, 'r') as f: + lines = f.readlines() + + new_lines = [] + changed = False + + for line in lines: + stripped = line.strip() + # 忽略空行、段名和已经是原生的配置行 + if not stripped or stripped.startswith('[') or stripped.startswith(';') or stripped.startswith('show_name'): + new_lines.append(line) + continue + + if '=' in line: + parts = line.split('=', 1) + raw_key = parts[0].strip() + # 兼容前面可能被加了;;;的key重新解开的情况(以防跑多次) + key = raw_key.lstrip(';') + val = parts[1].strip() + + # 处理一些特有的布尔值或字符串转义差异 + if key == "print_sequence" and val == "by layer": + val = "0" + elif key == "print_sequence" and val == "by object": + val = "1" + if key == "spiral_mode": + val = "1" if val != "0" else "0" + if key == "support_type" and "auto" in val: + val = "1" + + if val == "zig-zag": + val = "zigzag" + + if key == "enable_arc_fitting": + if str(val) == "1": + val = "emit_center" + else: + val = "disabled" + + if key == "only_one_wall_top": + if str(val) == "1": + val = "top" + else: + val = "none" + + if key in SEMANTIC_MAP: + new_key = SEMANTIC_MAP[key] + new_lines.append(f"{new_key} = {val}\n") + changed = True + elif key in VALID_KEYS: + # 已经是PrusaSlicer的原生可用属性 + new_lines.append(f"{key} = {val}\n") + else: + # 在 all_keys.txt 中但找不到任何对应 PrusaSlicer 语义的属性 + new_lines.append(f";;;{raw_key} = {val}\n") + changed = True + else: + new_lines.append(line) + + if changed: + with open(filepath, 'w') as f: + f.writelines(new_lines) + +for root, dirs, files in os.walk('print_config/prusa_slicer'): + for file in files: + if file.endswith('.ini'): + process_file(os.path.join(root, file)) +print("All keys mapped exhaustively.") diff --git a/scripts/tmp_get_ini_from_json.py b/scripts/tmp_get_ini_from_json.py new file mode 100644 index 0000000..27d3844 --- /dev/null +++ b/scripts/tmp_get_ini_from_json.py @@ -0,0 +1,21 @@ +import json +import os + + +if __name__ == "__main__": + all_files = os.sys.argv[1:] + print(all_files) + keys = {} + for file in all_files: + js = json.load(open(file)) + for k, v in js.items(): + if k in ['filament_id','setting_id',"type","name","from","instantiation","inherits","compatible_printers","filename_format"]: + continue + if type(v) == list: + v = v[0] + keys[k] = v + print(json.dumps(keys)) + with open("0.08mm_Extra_Fine.ini", "w") as f: + for k, v in keys.items(): + v_str = v.replace('\n', '\\n') + f.write(f"{k} = {v_str}\n") \ No newline at end of file diff --git a/scripts/valid_keys.txt b/scripts/valid_keys.txt new file mode 100644 index 0000000..2c9f3d8 --- /dev/null +++ b/scripts/valid_keys.txt @@ -0,0 +1,417 @@ +load +material_profile +print_profile +printer_profile +export_3mf +export_gcode +gcode +export_obj +export_sla +sla +export_stl +gcodeviewer +help +help_fff +help_sla +info +query_print_filament_profiles +query_printer_models +save +slice +align_xy +center +cut +dont_arrange +duplicate +duplicate_grid +ensure_on_bed +no_ensure_on_bed +merge +rotate +rotate_x +rotate_y +scale +scale_to_fit +split +config_compatibility +datadir +delete_after_load +ignore_nonexistent_config +load +loglevel +opengl_aa +output +single_instance +threads +fill_pattern +load +arc_fitting +autoemit_temperature_commands +avoid_crossing_curled_overhangs +avoid_crossing_perimeters +bed_custom_model +bed_custom_texture +bed_shape +bed_temperature +before_layer_gcode +between_objects_gcode +binary_gcode +bridge_acceleration +bridge_fan_speed +chamber_minimal_temperature +chamber_temperature +color_change_gcode +colorprint_heights +complete_objects +cooling +cooling_perimeter_transition_distance +cooling_slowdown_logic +cooling_tube_length +cooling_tube_retraction +custom_parameters_filament +custom_parameters_print +custom_parameters_printer +default_acceleration +deretract_speed +disable_fan_first_layers +draft_shield +duplicate_distance +enable_dynamic_fan_speeds +end_filament_gcode +end_gcode +external_perimeter_acceleration +extra_loading_move +extruder_clearance_height +extruder_clearance_radius +extruder_colour +extruder_offset +extrusion_axis +extrusion_multiplier +fan_always_on +fan_below_layer_time +filament_abrasive +filament_colour +filament_cooling_final_speed +filament_cooling_initial_speed +filament_cooling_moves +filament_cost +filament_density +filament_deretract_speed +filament_diameter +filament_infill_max_crossing_speed +filament_infill_max_speed +filament_load_time +filament_loading_speed +filament_loading_speed_start +filament_max_volumetric_speed +filament_minimal_purge_on_wipe_tower +filament_multitool_ramming +filament_multitool_ramming_flow +filament_multitool_ramming_volume +filament_notes +filament_purge_multiplier +filament_ramming_parameters +filament_retract_before_travel +filament_retract_before_wipe +filament_retract_layer_change +filament_retract_length +filament_retract_length_toolchange +filament_retract_lift +filament_retract_lift_above +filament_retract_lift_below +filament_retract_restart_extra +filament_retract_restart_extra_toolchange +filament_retract_speed +filament_seam_gap_distance +filament_shrinkage_compensation_xy +filament_shrinkage_compensation_z +filament_soluble +filament_spool_weight +filament_stamping_distance +filament_stamping_loading_speed +filament_toolchange_delay +filament_travel_lift_before_obstacle +filament_travel_max_lift +filament_travel_ramping_lift +filament_travel_slope +filament_type +filament_unload_time +filament_unloading_speed +filament_unloading_speed_start +filament_wipe +first_layer_acceleration +first_layer_acceleration_over_raft +first_layer_bed_temperature +first_layer_infill_speed +first_layer_speed +first_layer_speed_over_raft +first_layer_temperature +full_fan_speed_layer +gcode_comments +gcode_flavor +gcode_label_objects +gcode_resolution +gcode_substitutions +high_current_on_filament_swap +infill_acceleration +infill_first +after_layer_gcode +layer_gcode +max_fan_speed +max_layer_height +max_print_height +max_print_speed +max_volumetric_extrusion_rate_slope_negative +max_volumetric_extrusion_rate_slope_positive +max_volumetric_speed +min_fan_speed +min_layer_height +min_print_speed +min_skirt_length +multimaterial_purging +notes +nozzle_diameter +nozzle_high_flow +only_retract_when_crossing_perimeters +ooze_prevention +output_filename_format +overhang_fan_speed_0 +overhang_fan_speed_1 +overhang_fan_speed_2 +overhang_fan_speed_3 +parking_pos_retraction +pause_print_gcode +perimeter_acceleration +post_process +prefer_clockwise_movements +preset_name +preset_names +printer_model +printer_notes +printer_technology +printer_variant +remaining_times +resolution +retract_before_travel +retract_before_wipe +retract_layer_change +retract_length +retract_length_toolchange +retract_lift +retract_lift_above +retract_lift_below +retract_restart_extra +retract_restart_extra_toolchange +retract_speed +seam_gap_distance +silent_mode +single_extruder_multi_material +single_extruder_multi_material_priming +skirt_distance +skirt_height +skirts +slowdown_below_layer_time +solid_infill_acceleration +solid_layers +solid_min_thickness +spiral_vase +staggered_inner_seams +standby_temperature_delta +start_filament_gcode +start_gcode +temperature +template_custom_gcode +thumbnails +thumbnails_format +toolchange_gcode +top_solid_infill_acceleration +travel_acceleration +travel_lift_before_obstacle +travel_max_lift +travel_ramping_lift +travel_short_distance_acceleration +travel_slope +travel_speed +travel_speed_z +use_firmware_retraction +use_relative_e_distances +use_volumetric_e +variable_layer_height +wipe +wipe_tower +wipe_tower_acceleration +wipe_tower_bridging +wipe_tower_brim_width +wipe_tower_cone_angle +wipe_tower_extra_flow +wipe_tower_extra_spacing +wipe_tower_no_sparse_layers +wipe_tower_width +wiping_volumes_matrix +wiping_volumes_use_custom_matrix +z_offset +bridge_flow_ratio +elefant_foot_compensation +infill_anchor +infill_anchor_max +infill_overlap +interlocking_beam +interlocking_beam_layer_count +interlocking_beam_width +interlocking_boundary_avoidance +interlocking_depth +interlocking_orientation +min_bead_width +min_feature_size +mmu_segmented_region_interlocking_depth +mmu_segmented_region_max_width +slice_closing_radius +slicing_mode +wall_distribution_count +wall_transition_angle +wall_transition_filter_deviation +wall_transition_length +xy_size_compensation +bed_temperature_extruder +extruder +infill_extruder +perimeter_extruder +solid_infill_extruder +support_material_extruder +support_material_interface_extruder +wipe_tower_extruder +automatic_extrusion_widths +external_perimeter_extrusion_width +extrusion_width +first_layer_extrusion_width +infill_extrusion_width +perimeter_extrusion_width +solid_infill_extrusion_width +support_material_extrusion_width +top_infill_extrusion_width +fuzzy_skin +fuzzy_skin_point_dist +fuzzy_skin_thickness +automatic_infill_combination +automatic_infill_combination_max_layer_height +bottom_fill_pattern +external_fill_pattern +solid_fill_pattern +bridge_angle +fill_angle +fill_density +fill_pattern +infill_every_layers +solid_infill_below_area +solid_infill_every_layers +top_fill_pattern +external_fill_pattern +solid_fill_pattern +ironing +ironing_flowrate +ironing_spacing +ironing_type +avoid_crossing_perimeters_max_detour +bottom_solid_layers +bottom_solid_min_thickness +ensure_vertical_shell_thickness +external_perimeters_first +extra_perimeters +extra_perimeters_on_overhangs +first_layer_height +gap_fill_enabled +interface_shells +layer_height +only_one_perimeter_first_layer +overhangs +perimeter_generator +perimeters +scarf_seam_entire_loop +scarf_seam_length +scarf_seam_max_segment_length +scarf_seam_on_inner_perimeters +scarf_seam_only_on_smooth +scarf_seam_placement +scarf_seam_start_height +seam_position +thick_bridges +thin_walls +top_one_perimeter_type +top_solid_layers +top_solid_min_thickness +machine_limits_usage +machine_max_acceleration_e +machine_max_acceleration_extruding +machine_max_acceleration_retracting +machine_max_acceleration_travel +machine_max_acceleration_x +machine_max_acceleration_y +machine_max_acceleration_z +machine_max_feedrate_e +machine_max_feedrate_x +machine_max_feedrate_y +machine_max_feedrate_z +machine_max_jerk_e +machine_max_jerk_x +machine_max_jerk_y +machine_max_jerk_z +machine_max_junction_deviation +machine_min_extruding_rate +machine_min_travel_rate +brim_separation +brim_type +brim_width +bridge_speed +enable_dynamic_overhang_speeds +external_perimeter_speed +gap_fill_speed +infill_speed +ironing_speed +over_bridge_speed +overhang_speed_0 +overhang_speed_1 +overhang_speed_2 +overhang_speed_3 +perimeter_speed +small_perimeter_speed +solid_infill_speed +top_solid_infill_speed +dont_support_bridges +raft_contact_distance +raft_expansion +raft_first_layer_density +raft_first_layer_expansion +raft_layers +support_material +support_material_angle +support_material_auto +support_material_bottom_contact_distance +support_material_bottom_interface_layers +support_material_buildplate_only +support_material_closing_radius +support_material_contact_distance +support_material_enforce_layers +support_material_interface_contact_loops +support_material_interface_layers +support_material_interface_pattern +support_material_interface_spacing +support_material_interface_speed +support_material_pattern +support_material_spacing +support_material_speed +support_material_style +support_material_synchronize_layers +support_material_threshold +support_material_with_sheath +support_material_xy_spacing +support_tree_angle +support_tree_angle_slow +support_tree_branch_diameter +support_tree_branch_diameter_angle +support_tree_branch_diameter_double_wall +support_tree_branch_distance +support_tree_tip_diameter +support_tree_top_rate +wipe_into_infill +wipe_into_objects +idle_temperature diff --git a/third_party/PRUSASLICER.md b/third_party/PRUSASLICER.md new file mode 100644 index 0000000..0b67885 --- /dev/null +++ b/third_party/PRUSASLICER.md @@ -0,0 +1,32 @@ +PrusaSlicer — 许可与来源说明 +================================ + +组件: PrusaSlicer + +- 许可证: GNU Affero General Public License v3 (AGPLv3) +- 官方源代码仓库: https://github.com/prusa3d/PrusaSlicer +- 本安装脚本中引用/下载的二进制(AppImage)仓库: https://github.com/davidk/PrusaSlicer-ARM.AppImage + +重要说明 +--------- + +1. PrusaSlicer 使用 AGPLv3 许可。AGPLv3 与 GPLv3 相比,增加了在网络服务场景下的源码披露要求:若你提供基于该软件的网络服务,可能需要向使用该服务的用户提供对应源码。 +2. 本项目的安装脚本可选择从上游仓库下载 PrusaSlicer 二进制;该行为在技术上由用户的机器直接从上游获取二进制,不等同于本仓库把二进制“随仓库分发”。但如果你在自己的服务器或镜像上分发这些二进制,仍视为分发行为,需要遵守 AGPLv3 的相应要求(包含提供对应源码)。 +3. 如果你修改了 PrusaSlicer 源码或将其与本项目深度整合(产生衍生作品),则该衍生作品整体需遵守 AGPLv3 的条款。 + +如何获取源码 +------------- + +官方源码(推荐): + +```bash +git clone https://github.com/prusa3d/PrusaSlicer.git +``` + +如果你需要与二进制精确对应的源码(例如同一 release/tag),请在官方仓库中检出对应的 tag/commit。 + +更多信息 +--------- + +- AGPLv3 许可证文本请参见官方仓库或本项目中可能随附的 `prusaslicer/PrusaSlicer/LICENSE` 文件。 +- 本文档仅为合规性提示,不构成法律意见。如需确定法律义务或风险,请咨询专业律师。