Add Windows build script and update README with build instructions

This commit is contained in:
hyyz17200
2026-05-08 12:42:37 +08:00
parent 4ba654ca3f
commit 6ac5331932
5 changed files with 251 additions and 4 deletions
+196 -2
View File
@@ -1,5 +1,7 @@
# A1 Swap Mod Packer
Current version: **v0.5.0**
A1 Swap Mod Packer is an open-source implementation 3MF packer for Bambu Lab A1 SwapMod workflows.
It takes one or more A1-sliced `.3mf` files, repeats their plate G-code according to copy counts, inserts an external SwapMod ejection/swap G-code block, and writes a new packed `.3mf` that can be sent to the printer.
@@ -24,6 +26,8 @@ It takes one or more A1-sliced `.3mf` files, repeats their plate G-code accordin
- External SwapMod G-code template folder.
- Editable G-code patch file.
- Optional remaining-time plate-number encoding through `M73 R...`.
- Selected-input thumbnail preview in the GUI.
- Packed preview image composition from up to 9 unique input files, with a short `{plates} P` label.
- Combined mode: all input rows become one packed 3MF.
- Individual batch mode: every input row becomes its own packed 3MF.
- zlib-ng Deflate ZIP compression level, defaulting to Level 7.
@@ -33,11 +37,22 @@ It takes one or more A1-sliced `.3mf` files, repeats their plate G-code accordin
Python 3.10 or newer is recommended.
Install dependency:
Install dependencies:
```bash
pip install -r requirements.txt
```
Current runtime dependencies:
| Dependency | Used for |
|---|---|
| `PySide6` | GUI application, drag-and-drop table, file dialogs, thumbnail preview, and settings UI |
| `Pillow` | Reading and composing PNG preview images inside the output 3MF, including the `{plates} P` label |
| `zlib-ng` | Required ZIP Deflate backend for writing compressed 3MF files and honoring the compression level |
No Java runtime, Bambu Studio SDK, external archive tool, or encrypted/template decoder is required by the current Python source version.
Run the GUI:
```bash
@@ -62,6 +77,105 @@ or
python run_cli.py
```
Show the version:
```bash
python -m a1_swap_mod_packer.cli --version
```
## Build Windows executables
The repository includes a Windows build script:
```cmd
build_win.cmd
```
The script uses Nuitka onefile mode and creates this portable release folder:
```text
build/onefile/
```
Expected output:
```text
build/onefile/
a1packer.exe
a1packer-cli.exe
gcode_patches.ini
swap_gcode/
settings.json Optional; created by the GUI after settings are saved
```
External resources are intentionally kept outside the executables. Do not pack these files into the onefile binary:
```text
swap_gcode/
gcode_patches.ini
settings.json
```
The application resolves these paths from the executable folder when running as a packaged exe.
### Windows build requirements
Tested build environment:
- Windows 10.
- Python 3.10 or newer.
- A virtual environment at `.venv/`.
- Project runtime dependencies from `requirements.txt`.
- Nuitka with onefile support.
- Microsoft C++ Build Tools / Visual Studio Build Tools with the MSVC compiler and Windows SDK.
Install the Python dependencies in a fresh virtual environment:
```cmd
py -3.12 -m venv .venv
.venv\Scripts\python.exe -m pip install --upgrade pip
.venv\Scripts\python.exe -m pip install -r requirements.txt
.venv\Scripts\python.exe -m pip install "Nuitka[onefile]"
```
Then run:
```cmd
build_win.cmd
```
The build script performs two Nuitka builds:
- GUI: `run_gui.py` -> `build/onefile/a1packer.exe`
- CLI: `run_cli.py` -> `build/onefile/a1packer-cli.exe`
The GUI build enables the PySide6 plugin and includes the Qt plugin groups needed by the current interface:
```text
platforms,imageformats,styles,iconengines
```
### After building
Before publishing the folder, verify the CLI can see the external resources:
```cmd
build\onefile\a1packer-cli.exe --version
build\onefile\a1packer-cli.exe list-swap-gcode
```
The second command should list files from:
```text
build/onefile/swap_gcode/
```
Also start `build/onefile/a1packer.exe` once and confirm that:
- The Swap G-code dropdown lists the copied templates.
- Editing GUI options creates or updates `build/onefile/settings.json`.
- The app still runs if `settings.json` is absent before first launch.
## Swap G-code templates
The GUI automatically scans this fixed folder:
@@ -70,7 +184,16 @@ The GUI automatically scans this fixed folder:
swap_gcode/
```
Plain UTF-8 G-code files are read directly.
Plain UTF-8 text files are read directly. The current scanner accepts these suffixes:
```text
.gcode
.nc
.ngc
.txt
```
The current source version does not decode encrypted or vendor-template G-code archives.
In the GUI:
@@ -143,6 +266,8 @@ If the source 3MF lacks this metadata, the GUI shows `Unknown`.
The summary line under the input list shows the total plate count, estimated time, and filament for the current table.
The right-side thumbnail panel shows the selected input file's active plate preview when one is available in the 3MF.
### Build 3MF button
The **Build 3MF** button is placed below the input list on the right side for fast batch operation.
@@ -236,6 +361,21 @@ Options:
- **Sum prediction and filament**
Updates the first plate metadata using the sum of all repeated source plates. This is more logically correct for statistics, but may differ from the vendor software.
### Preview image handling
By default, the output 3MF keeps the base archive preview members for the active output plate and rewrites those PNG previews with:
- a composite of up to 9 unique input-file preview images;
- a short green label such as `5 P`, applied once to the final composite.
If preview images are missing or cannot be read, the packer keeps the available base preview and still tries to apply the short plate label.
The CLI can disable preview rewriting with:
```bash
--no-preview-label
```
### Batch mode
#### Combined mode
@@ -363,6 +503,12 @@ Saved options include:
## CLI examples
List available Swap G-code files:
```bash
python -m a1_swap_mod_packer.cli list-swap-gcode
```
Build one source with five copies:
```bash
@@ -405,3 +551,51 @@ python -m a1_swap_mod_packer.cli build \
-o "5 Plates - A.3mf"
```
Build positional inputs with the same copy count:
```bash
python -m a1_swap_mod_packer.cli build \
"A.3mf" "B.3mf" \
--copies 2 \
--swap-gcode "a1_swap.gcode" \
-o "4 Plates - A_and_B.3mf"
```
Use an explicit Swap G-code folder and ZIP compression level:
```bash
python -m a1_swap_mod_packer.cli build \
--item "A.3mf" 5 \
--swap-gcode "a1_swap.gcode" \
--swap-gcode-dir "D:\SwapMod\swap_gcode" \
--zip-level 9 \
-o "5 Plates - A.3mf"
```
Disable final swap and preview label rewriting:
```bash
python -m a1_swap_mod_packer.cli build \
--item "A.3mf" 5 \
--swap-gcode "a1_swap.gcode" \
--no-swap-after-final \
--no-preview-label \
-o "5 Plates - A.3mf"
```
Useful CLI options:
| Option | Meaning |
|---|---|
| `--version` | Print the current version |
| `list-swap-gcode` | List files found in the Swap G-code folder |
| `--item PATH COPIES` | Add one input with its own copy count; can be repeated |
| positional `inputs` + `--copies N` | Add multiple inputs with the same copy count |
| `--swap-gcode-dir DIR` | Use a custom Swap G-code folder |
| `--no-bed-cooldown` | Do not insert `M190` before the swap block |
| `--no-swap-after-final` | Do not run the swap block after the final plate |
| `--line-ending lf|crlf` | Choose generated G-code line endings; default is `crlf` |
| `--zip-level 1-9` | zlib-ng Deflate compression level; default is `7` |
| `--no-preview-label` | Do not rewrite the preview image label/composite |
| `--no-gcode-patches` | Do not apply rules from `gcode_patches.ini` |
+1 -1
View File
@@ -1,3 +1,3 @@
APP_NAME = "A1 Swap Mod Packer"
__version__ = "0.4.0"
__version__ = "0.5.0"
APP_TITLE = f"{APP_NAME} v{__version__}"
+1 -1
View File
@@ -94,7 +94,7 @@ def create_parser() -> argparse.ArgumentParser:
build.add_argument("--metadata-mode", choices=("source", "sum"), default="source", help="How to write slice_info prediction and weight.")
build.add_argument("--line-ending", choices=("lf", "crlf"), default="crlf", help="Line ending for the generated G-code.")
build.add_argument("--zip-level", type=int, choices=range(1, 10), default=DEFAULT_ZIP_COMPRESS_LEVEL, metavar="1-9", help="zlib-ng Deflate compression level for the output 3MF. Default: 7.")
build.add_argument("--no-preview-label", action="store_true", help="Do not add a plate-count label to the first preview image.")
build.add_argument("--no-preview-label", action="store_true", help="Do not rewrite the preview image label/composite.")
build.add_argument("--no-gcode-patches", action="store_true", help=f"Do not apply editable patches from {default_patch_config_path()}.")
build.set_defaults(func=build_command)
+10
View File
@@ -9,6 +9,16 @@ PATCH_CONFIG_FILE_NAME = "gcode_patches.ini"
def program_root() -> Path:
compiled = globals().get("__compiled__")
if compiled is not None and getattr(compiled, "onefile", False):
containing_dir = getattr(compiled, "containing_dir", None)
if containing_dir:
return Path(containing_dir).resolve()
original_argv0 = getattr(compiled, "original_argv0", None)
if original_argv0:
return Path(original_argv0).resolve().parent
if getattr(sys, "frozen", False):
return Path(sys.executable).resolve().parent
return Path(__file__).resolve().parents[1]
+43
View File
@@ -0,0 +1,43 @@
@echo off
chcp 65001 >nul
setlocal
set "PY=.venv\Scripts\python.exe"
set "DIST=build\onefile"
if not exist "%DIST%" mkdir "%DIST%"
"%PY%" -m nuitka ^
--mode=onefile ^
--msvc=latest ^
--enable-plugin=pyside6 ^
--include-qt-plugins=platforms,imageformats,styles,iconengines ^
--windows-console-mode=disable ^
--output-dir="%DIST%" ^
--output-filename=a1packer.exe ^
--remove-output ^
run_gui.py
if errorlevel 1 exit /b %ERRORLEVEL%
echo.
echo GUI build done.
echo Next step is building CLI.
"%PY%" -m nuitka ^
--mode=onefile ^
--msvc=latest ^
--output-dir="%DIST%" ^
--output-filename=a1packer-cli.exe ^
--remove-output ^
run_cli.py
if errorlevel 1 exit /b %ERRORLEVEL%
robocopy "swap_gcode" "%DIST%\swap_gcode" /E
if %ERRORLEVEL% GEQ 8 exit /b %ERRORLEVEL%
copy /Y "gcode_patches.ini" "%DIST%\gcode_patches.ini" >nul
if errorlevel 1 exit /b %ERRORLEVEL%
echo.
echo Build done: %DIST%
exit /b 0