Windows-утилита: принимает список нарезанных файлов (.ctb), парсит из
каждого время печати, объём и массу смолы, показывает сводную таблицу и
итоги (в т.ч. стоимость по заданной цене смолы за кг), умеет сохранять и
открывать проект, экспортировать результат в CSV.
Архитектура: C++ библиотека-парсер (DLL, плоский C API) + Python/tkinter
GUI, вызывающий DLL через ctypes.
Как устроен сам формат .ctb и что про него выяснено —
docs/format_notes.md.
Поддерживаемый на сегодня вариант формата — CTBv4 с зашифрованным
служебным заголовком (magic 0x12FD0107; такой создают, например, новые
версии Chitubox и принтеры вроде Elegoo Mars 3).
Годится для правки логики парсера и GUI и прогона тестов — не даёт
Windows-.exe.
# C++ core + юнит-тесты (GoogleTest подтягивается через FetchContent)
cmake -S . -B build
cmake --build build -j
./build/bin/slicer_parser_tests
# Python: биндинги + модель + GUI
python3 -m venv .venv && source .venv/bin/activate
pip install -r python/requirements.txt
python3 -m pytest python/tests/
python3 python/gui/app.pyНе требует переключения в PowerShell/cmd и Visual Studio Build Tools —
достаточно MinGW-w64 (x86_64-w64-mingw32-gcc/g++), установленного внутри
WSL2. Собранный кросс-компилятором .exe можно сразу запускать прямо из
WSL bash — WSL передаёт такой процесс настоящему ядру Windows (interop),
Wine не нужен.
-
C++ core (DLL + тесты):
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=cmake/mingw-w64-x86_64.toolchain.cmake cmake --build build -j ./build/bin/slicer_parser_tests.exeРезультат —
build/bin/slicer_parser.dllиbuild/bin/slicer_parser_tests.exe, оба без внешних рантайм-DLL (статически слинкованы libgcc/libstdc++/libwinpthread). Тесты гоняются на файлах изexamples/через настоящий Windows-процесс — если что-то не так с окружением, это будет видно сразу (проверено: 8/8 тестов зелёные).Если до этого
build/уже был сконфигурирован под Linux (.so) — тулчейн в существующем кэше не подменить, сначалаrm -rf build. -
Python-окружение и упаковка — те же шаги и команды, что в Варианте 2 (пп. 2-3): DLL из этой сборки
parser_bindings.pyиpyinstaller.specнаходят по тому же путиbuild/bin/, без правок. Учтите только, что PyInstaller не кросс-компилирует — самpyinstallerвсё равно нужно запускать под настоящим Windows-интерпретатором Python (не под Python из WSL), даже если DLL внутри собрана в WSL.
Если WSL или MinGW недоступны: из PowerShell/cmd с установленными Visual Studio Build Tools (или полной Visual Studio) с компонентом "Desktop development with C++".
-
C++ core (DLL):
cmake -S . -B build -A x64 cmake --build build --config Release
Результат —
build\bin\Release\slicer_parser.dllиbuild\bin\Release\slicer_parser_tests.exe. Прогоните тесты (.\build\bin\Release\slicer_parser_tests.exe) — все они гоняются на файлах изexamples/, если что-то не так с окружением, это будет видно сразу. -
Python-окружение:
py -m venv .venv .venv\Scripts\activate pip install -r python\requirements.txt python -m pytest python\tests\ python python\gui\app.py # ручная проверка GUI
-
Упаковка в портативный
.exe(PyInstaller подхватит DLL изbuild\bin\Release\илиbuild\bin\— см.python/packaging/pyinstaller.spec):cd python pyinstaller packaging\pyinstaller.spec
Готовый файл —
python\dist\chitubox_batch_analyzer.exe.
ctb-batch-analyzer/
├── README.md # этот файл
├── Chitubox_batch_analyzer.exe # собранный exe-файл для запуска на Windows
├── CMakeLists.txt # корневой, add_subdirectory(core)
├── .clang-format
├── .gitignore
│
├── cmake/
│ └── mingw-w64-x86_64.toolchain.cmake # кросс-сборка Windows-DLL из WSL2
│
├── examples/ # эталонные .cbddlp/.ctb файлы для тестов
│ └── example_*.ctb
│
├── screenshots/ # скриншоты GUI для README
│ └── screenshot.JPG
│
├── core/ # C++ парсер (DLL)
│ ├── CMakeLists.txt
│ ├── include/
│ │ └── slicer_parser/
│ │ ├── slicer_parser.h # публичный C API (extern "C")
│ │ └── slice_stats.h # layout SliceStats, общий с ctypes
│ ├── src/
│ │ ├── slicer_parser.cpp # диспетчер по magic (сейчас — 1 вариант)
│ │ ├── ctb_format.h/.cpp # структуры и парсинг CTBv4 encrypted
│ │ ├── aes256_cbc.h/.cpp # обёртка над вендоренной tiny-AES-c
│ │ └── third_party/tiny_aes/ # kokke/tiny-AES-c (Unlicense), не трогаем
│ └── tests/
│ ├── CMakeLists.txt # FetchContent(GoogleTest)
│ └── test_parse_file.cpp # эталонные значения из examples/
│
├── python/ # GUI + биндинги
│ ├── requirements.txt
│ ├── parser_bindings.py # ctypes-обёртка над DLL
│ ├── gui/
│ │ ├── __init__.py
│ │ ├── app.py # точка входа
│ │ ├── model.py # логика без tkinter (список файлов, итоги)
│ │ ├── project_io.py # сохранение/открытие проекта (JSON), без tkinter
│ │ ├── main_window.py
│ │ ├── assets/
│ │ │ ├── app_icon.png # значок окна/панели задач (root.iconphoto)
│ │ │ └── app_icon.ico # значок .exe (иконка в Проводнике/на ярлыке)
│ │ └── widgets/
│ │ ├── file_list.py
│ │ └── results_table.py
│ ├── tests/
│ │ ├── test_parser_bindings.py # сверка с теми же examples/
│ │ ├── test_model.py # пересчёт итогов/стоимости
│ │ └── test_project_io.py # round-trip сохранения/открытия проекта
│ └── packaging/
│ └── pyinstaller.spec
│
└── docs/
└── format_notes.md # реверс-инжиниринг офсетов, версии заголовка,
# что проверено/не проверено (живой документ)
Почему так:
core/иpython/— равноправные соседи верхнего уровня, а неsrc/core+src/python: это два разных языка и тулчейна (CMake и venv/pip), общийsrc/над ними ничего не даёт.cmake/— отдельно отcore/, потому что тулчейн-файл для кросс-компиляции не относится к конкретной библиотеке, а настраивает сам CMake ещё до всехadd_subdirectory(см. раздел «Сборка под Windows» выше).examples/— единственный источник эталонных файлов сразу для C++- и Python-тестов, без дублирования.core/include/slicer_parser/отделён отcore/src/: вinclude/— только то, что видит внешний потребитель DLL (в том числе Python/ctypes), вsrc/— внутренняя реализация.docs/format_notes.md— живой документ реверс-инжиниринга формата (офсеты, версии заголовка, какие профили Chitubox подтверждены), отдельно от остальной документации, чтобы конкретные технические факты не смешивались с общим описанием проекта.
- Стандарт C++17. Стиль — Google C++ Style Guide как база, форматирование —
через
.clang-formatв корне репозитория; прогонятьclang-formatперед коммитом. - Именование:
PascalCaseдля структур/типов (MainHeader,PrintParameters,SliceStats),snake_caseдля функций и переменных (parse_file,print_time_s). - Бинарные структуры (заголовки файлов формата):
- только типы фиксированной ширины (
uint32_t,int32_tи т.п.), никогдаint/long; #pragma pack(push, 1)/#pragma pack(pop)вокруг структуры;- сразу под структурой —
static_assert(sizeof(MainHeader) == N, "..."). Это единственная защита от того, что компилятор молча добавит padding и все офсеты «поплывут» — обязательный пункт, не опция.
- только типы фиксированной ширины (
- Граница C API (
extern "C"): только POD-структуры и коды возврата. Исключения не должны пересекать границу DLL — оборачивать тело функции вtry/catchи возвращать код ошибки. - Тесты: GoogleTest, подключается в
core/tests/CMakeLists.txtчерезFetchContent. Эталонные значения — из файлов вexamples/.
- PEP 8, типхинты везде, включая
parser_bindings.py— там типы документируют бинарный контракт с C++. SliceStatsна Python-стороне —ctypes.Structureс_pack_ = 1, побайтово зеркалирующая C++-структуру. Рядом —@dataclass-обёртка с «человеческими» типами (например,datetime.timedeltaвместо секунд);ctypes.Structureнапрямую в GUI-код не передавать.gui/не работает с ctypes напрямую — только черезparser_bindings.py. Внутриgui/: логика пересчёта (сумма времени/объёма/массы, стоимость) отделена от tkinter-виджетов, чтобы её можно было unit-тестировать без поднятия окна.pathlib.Pathвместоos.path;loggingвместоprintдля диагностики ошибок парсинга.- Тексты в интерфейсе (кнопки, заголовки колонок, статусы) — на русском, проект для личного использования на русском языке.