Приложение превращает картинку в многослойную филаментную картину для 3D-печати: загрузить изображение → сократить до 2–8 цветов (слайдер) → ступенчатая карта высот → скачать STL / 3MF. Цвет кодируется высотой слоя — как в HueForge.
- Установите Node.js LTS (один раз).
- Скачайте со страницы релизов архив
hueforge-web-vX.Y.Z.zipи распакуйте его. - Дважды щёлкните
install.batв распакованной папке — он:- проверит Node.js и поищет новую версию на GitHub (и предложит подтянуть её автоматически, если она есть),
- установит зависимости в папку проекта,
- предложит создать ярлык на рабочем столе,
- запустит приложение и откроет браузер.
Токен GitHub: при первом запуске
install.batпредложит вставить токен (создать: https://github.com/settings/personal-access-tokens — fine-grained, только репозиторийhf, права Contents: Read + Metadata: Read). Репозиторий публичный, поэтому от токена можно отказаться: без негоupdate.ps1ходит в API анонимно и обновления всё равно работают, а токен поднимает лимит запросов. Токен сохраняется только на этом ПК в%APPDATA%\HueForgeWeb\github_token.txtи используется исключительно для проверки и скачивания релизов.
- Дальше достаточно ярлыка или двойного щелчка по
start.bat— браузер откроется наhttp://127.0.0.1:5173(зависимости самовосстанавливаются, если папку перенесли или удалилиnode_modules).
Чтобы остановить сервер, закройте чёрное окно консоли. Версия установленной сборки видна в верхней панели приложения рядом с заголовком.
Есть ещё вариант «сервер + браузер» без установки: готовый пакет
hueforge-web-deploy-v*.zip с того же релиза (не нужны ни npm install, ни
исходники) — запускается deploy.bat на http://127.0.0.1:8080, порт можно задать
аргументом. Подробности — в DEPLOY.md.
То же приложение поставляется как нативное приложение Windows — HueForge Desktop: безрамочное окно с собственными кнопками заголовка, значок в панели задач и ярлык в меню «Пуск»; ни браузер, ни Node.js не нужны.
- Установка: возьмите
hueforge-desktop-setup-<version>.exeсо страницы релизов и запустите (установщик NSIS, x64; папку установки можно выбрать). - Автообновление встроено: приложение проверяет релизы GitHub при запуске (через 15 с) и далее каждые 4 часа. В верхней панели появляется индикатор (⟳ проверка, ↑ доступно обновление, % при скачивании, ↓ готово к установке — нажмите, чтобы действовать). Обновление ставится при выходе или сразу после подтверждения.
- Доступ к релизам по токену: в настольном приложении токен обязателен —
манифест обновления собран для доступа через GitHub API (fine-grained,
Contents: Read+Metadata: Readна этот репозиторий — тот же, что используетinstall.bat). Нажмите индикатор обновления, вставьте токен в диалоге — он будет сохранён зашифрованным на этом ПК (Electron safeStorage / DPAPI). Переменные окружения видаHF_GITHUB_TOKENнастольное приложение не читает. - Экспорт: «Скачать STL/3MF/…» открывает нативное окно Windows «Сохранить как»; в браузерной сборке файлы скачиваются как раньше.
Собрать из исходников: npm run dist (сначала нужен npm install). Установщик и
манифесты автообновления (latest.yml, blockmap) появляются в release/ и
прикрепляются к релизу GitHub — вручную или шагом workflow.
- Левая панель разбита на вкладки: Проект (изображение, цвета, режим глубины, размер, палитра), Проверка (печатаемость, производительность, эталон), Экспорт.
- Области предпросмотра («Исходное», карта высот, 3D-вид) располагаются схемами «друг над другом» или «два рядом»; пропорция тянется сплиттером, двойной щелчок возвращает 50/50, а полоса с шевроном внизу растит рабочую область по высоте. В узких окнах (меньше 900 px) схемы складываются в колонку.
- У каждой панели единый формат шапки: иконка → название → бейдж → «?» с подсказкой.
- Перетащите картинку (PNG / JPG / WebP) в панель слева.
- Выберите число цветов — от 2 до 8: тяните слайдер, щёлкайте по меткам 2 / 4 / 6 / 8 или введите число в поле. Слайдер в фокусе слушает стрелки.
- Выберите режим глубины — тёмные цвета выше (классический HueForge) или светлые выше. Если яркость картинки плохо описывает, что где ближе (портрет на фоне, предмет на столе), переключите источник рельефа на глубину — см. Источник рельефа.
- При необходимости поправьте размер (ширина и высота в мм), толщину основания и максимальную высоту.
- Загляните в панель проверки печати — она предупредит о слишком тонких полосах цвета, элементах тоньше сопла, большом числе смен филамента и хрупких изолированных участках, а для каждого предупреждения предложит конкретное исправление.
- Проверьте палитру — там каждый цвет с порядком печати (#1 = первый филамент) и ближайшим филаментом из библиотеки.
- Скачайте STL (цвет высотой слоя — смены филамента выставьте в слайсере на
высотах Z из панели палитры) или 3MF (та же геометрия плюс палитра и
порядок печати в метаданных). В варианте «сервер + браузер» с
deploy-slicer.batесть кнопка «Открыть в слайсере» — 3MF уходит сразу в Bambu Studio / OrcaSlicer / PrusaSlicer. - Сравните с эталоном — перетащите
.3mf(проект HueForge/Bambu или наш собственный экспорт), чтобы прочитать его размер, цвета и расписание смен и перенести их в свою работу одним нажатием, либо перетащите эталонный.stl, чтобы измерить его рельеф (габарит, шаг сетки, шаг высоты, число уровней высот и долю плато, склонов и стенок) и построчно сверить с нашим — с таблицей расхождений. - При необходимости откалибруйте τ по образцу: распечатайте образец для цвета, сфотографируйте его — приложение подберёт длину пропускания τ, и предпросмотры будут совпадать с реальной катушкой. По умолчанию τ = 0,3 мм — это типичный непрозрачный PLA: при такой длине пропускания миллиметровый лист читается как сплошной, поэтому предпросмотр показывает почти тот же цвет, который напечатается. Прозрачным катушкам τ ставят больше (вплоть до 6 мм) — тогда предпросмотр честно показывает, что нижние слои просвечивают.
Модель повторяет схему бесплатного Filapaint (cxwl.org) и режим HueForge Standard: сплошное основание + рельеф, высота которого следует яркости картинки. Параметры бесплатного инструмента ложатся на элементы приложения один к одному:
| Параметр Filapaint | Что это | Где в приложении |
|---|---|---|
| Base Thickness | сплошная подложка под рельефом | поле Основание |
| Relief Thickness | диапазон высот, по которому «рисует» картинка | Макс. высота − Основание (разложение печатается строкой под полями размера) |
| Layer Height | шаг высоты: поверхность живёт на сетке слоёв слайсера | поле Высота слоя |
| Contrast | как далеко тона расходятся по высоте рельефа | слайдер Контраст |
| Power Deepening | изгиб тональной кривой: куда ложатся полутона | слайдер Детали |
| Color layers | высоты, на которых меняется филамент | Палитра и порядок печати — высоты смен в мм |
Порядок обработки: яркость → контраст → проявление деталей → привязка высоты к сетке слоёв. Кривая строго монотонна и оставляет крайние тона на основании и на верхушке модели, поэтому весь диапазон высот используется всегда, а пиксель не может выйти из своей цветовой полосы: высоты смен двигаются вместе с рельефом, а не остаются на месте. Дизеринг, разбиение на полосы и назначение цветов пикселям считаются до тональной кривой, поэтому контраст и «Детали» не сдвигают ни одного пикселя цвета — меняется только высота.
Замер на тестовой картинке 320×240 (4 цвета, 0,8–8 мм, слой 0,2 мм):
| Настройка | Высоты смен филамента | Уровней высот |
|---|---|---|
| нейтрально (100/100) | 3,60 / 4,40 / 5,20 мм | 37 |
| контраст 200% | 3,00 / 4,60 / 6,00 мм | 37 |
| контраст 50% | 4,00 / 4,40 / 4,80 мм | 35 |
| детали 200% | 1,80 / 2,60 / 3,60 мм | 37 |
Контраст раздвигает смены к краям, «Детали» больше 100% опускает полутона (смены уезжают вниз), меньше 100% — поднимает. При этом поверхность всё равно живёт на сетке слоёв: 35–37 уровней из 37 возможных на рельефе 7,2 мм.
Два процента («Контраст», «Детали») задают настройку точно, но не говорят, что
получится. Поэтому над ними стоит ряд именованных стилей — каждый выставляет оба
ползунка сразу (src/lib/tonePresets.ts):
| Стиль | Контраст | Детали | Что делает |
|---|---|---|---|
| Мягко | 60 % | 90 % | тона держатся ближе к середине: склоны плавные, ступеней почти нет |
| Как на фото | 100 % | 100 % | высота повторяет яркость один к одному, ничего не преувеличено |
| Глубокий рельеф | 130 % | 250 % | полутона уходят к основанию, светлые участки остаются высокими |
| Графика | 200 % | 100 % | тона расходятся к краям, переходы между цветами резкие |
Стиль — это те же два ползунка, а не отдельный режим: выбрав стиль, его можно тут же уточнить, и тогда выбор помечается бейджем «Свой» (в это же состояние попадает результат автоподбора по эталону). Стиль запоминается вместе с проектом и сохраняется в localStorage, как любая другая настройка.
Пол рельефа: стиль не может сжать цветовую полосу ниже сопла (0,40 мм + один слой запаса на округление по сетке слоёв). Кривая тона двигает высоты смен вместе с рельефом, поэтому сильный стиль вроде «Глубокого рельефа» умел сжать нижний цвет до одного слоя — такой лист почти прозрачен, и тени печатались одним мутным пятном вместо трёх разных цветов. Теперь границы полос поднимаются до минимума монотонным пересчётом: высоты пересчитываются той же кривой, и пиксель не может выйти из своей полосы. На тестовой картинке (4 цвета, 0,8–8 мм, слой 0,2 мм) нижняя полоса «Глубокого рельефа» из 0,20 мм стала 0,60 мм — вместо предупреждения «тоньше сопла» проверка печатаемости показывает чистые переходы.
Переключатель в панели Цвета задаёт, откуда пиксель берёт филамент
(src/lib/quantize.ts, mapToLuminanceBands и mapToImageColors):
| Режим | Палитра | Деталь |
|---|---|---|
| По яркости (HueForge) | средний цвет каждого среза яркости | яркость задаёт высоту, полоса высоты — цвет; то, что документировано выше и с чем сверяется эталонная модель |
| По цветам изображения | median cut по RGB самой картинки | пиксель берёт ближайший цвет, каждый цвет получает равную долю высоты |
Разница проявляется там, где яркость не различает детали. На тестовом закате (детализированная картинка, теневой герой на фоне красного неба) палитра «по яркости» — три тёмно-коричневых плюс оранжевый: красное небо усредняется со светлым камнем в коричневый, а тень лица неотличима по яркости от плаща. При 6 цветах «по цветам изображения» палитра получается такой:
| Режим | Палитра (6 цветов) |
|---|---|
| по яркости | #312b2d #792f2c #9f382a #a05133 #bd6435 #d1943b |
| по цветам изображения | #433129 #4c4243 #dc3318 #71655d #c48049 #df8a1d |
То есть небо печатается красным, а не коричневым, и детали, совпадающие по яркости, но разные по оттенку, получают каждая свой филамент. Внутри каждой террасы светотень по-прежнему следует локальной яркости, поэтому цветовое пятно остаётся вылепленным, а не плоской плитой; высота всегда попадает в долю своего цвета, так что верхний слой в любой точке — тот филамент, который показывает предпросмотр.
Режим «по цветам изображения» больше не следует модели «высота следует яркости»,
поэтому эталонная модель HueForge с ним не совпадёт — для сверки с эталоном
остаётся режим «по яркости» (он же режим по умолчанию и то, что записывают старые
проекты без поля colorMode).
Цвет = высота означает, что каждая граница цвета обязана быть ступенью в 1/n высоты: два соседних пикселя разных филаментов не могут стоять на одной высоте. На детализированной картинке таких границ сотни, и без обработки модель печатается частоколом вертикальных шипов (замер тестового заката, 6 цветов: 806 областей одного цвета, у 5,4 % соседних пар перепад две полосы и больше). Поэтому после раскладки геометрия проходит ограничитель крутизны: ни один пиксель не может стоять выше соседа больше чем на два слоя высоты на клетку (45° — самый крутой свес, который FDM печатает без поддержек). Ступень в 1,2 мм превращается в плечо шириной 3 клетки, плоские площадки не трогаются, а цвета не меняются: у самой границы верхний слой просто попадает в тот или другой филамент на клетку раньше или позже. Широкое сглаживание рельефа (ползунок «Сглаживание») в этом режиме не применяется — оно размыло бы сами террасы и увело бы поверхность из своей полосы; там ползунок чистит только цвета.
Замер на тестовом градиенте 64×64 (4 цвета, 0,6–8 мм, слой 0,2 мм) — разброс поверхности по высоте (дисперсия распределения, в корзинах 16-уровневого профиля):
| Стиль | Разброс | Центр масс |
|---|---|---|
| Мягко | 9,1 | 8,5 |
| Как на фото | 15,7 | 8,1 |
| Глубокий рельеф | 22,3 | 4,8 |
| Графика | 26,5 | 8,2 |
То есть «Мягко» и вправду собирает поверхность вместе, «Глубокий рельеф» опускает
её массу почти вдвое ниже середины, а «Графика» расходится шире всех — эти
свойства закреплены тестами (src/test/tonePresets.test.ts), а не только названием.
Полный диапазон высот при этом используют все стили: кривая прибита к 0 и 1, поэтому
самый тёмный и самый светлый тон остаются на основании и на верхушке модели.
Под стилями печатается итоговая строка — она отвечает на вопрос «что выйдет»: основание + рельеф = общая высота, число цветов и высоты, на которых меняется филамент (те же числа, что в строках палитры и в модели).
Текущую настройку можно сохранить под своим именем: введите название в поле «Название стиля» и нажмите «Сохранить стиль» — рядом с готовыми стилями появится ваш собственный, а имя применяет его так же, как встроенное. Крестик на чипе удаляет стиль. Имя не обязательно: пустое поле сохранит стиль как «Мой стиль», а настройка, у которой уже есть сохранённый стиль, второй раз не сохраняется — в подписи будет назван существующий (список вмещает 24 стиля).
Стиль хранится в двух местах (src/lib/customTones.ts):
| Где | Что даёт |
|---|---|
localStorage (hf-custom-tones) |
ваши стили остаются между запусками, независимо от открытого проекта |
файл проекта (settings.customTones) |
проект переносит свои стили на другую машину — при открытии они встают в ряд рядом с местными |
Стиль — это те же два числа (контраст и детали), а не копия картинки, поэтому
сохранение и удаление не трогают геометрию: сохранённый стиль в выделенном виде
подсвечивается, а его значения можно тут же уточнить ползунками. Идентификаторы
сохраняются при переносе через файл, поэтому повторное открытие того же проекта не
плодит дубли. Правила списка и чтение/запись в файл закреплены тестами
(src/test/customTones.test.ts): дубликат по значениям, обрезка длинного имени,
отказ на испорченный список, круговорот «проект → браузер → проект».
По умолчанию высота пикселя следует его яркости. Но яркость не знает, что ближе: тёмный фон и светлое лицо на нём получают разную высоту не потому, что лицо ближе, а потому что оно светлее, и портрет на светлой стене выходит плоским. В блоке «Режим глубины» → «Источник рельефа» три варианта:
| Источник | Откуда высота | Когда брать |
|---|---|---|
| Яркость картинки (по умолчанию) | яркость пикселя; всё как описано выше | графика, логотипы, эталон HueForge |
| Глубина (нейросеть, офлайн) | относительная глубина, которую оценивает Depth Anything V2 Small | портреты, предметы на фоне, пейзажи с планами |
| Карта глубины из файла (PNG) | ваша готовая карта глубины или высот | глубина из другой программы, Blender, скульптора, 3D-реконструкции |
Как это устроено. Слои по-прежнему режутся так, чтобы у каждого цвета была своя доля высоты, но порядок задаёт глубина: ближе — выше. Область с одинаковой глубиной (плоский фон, плоский передний план) целиком попадает в один слой — фон вырезки остаётся на основании, а не режется на полосы. Цвет каждого филамента — это средний цвет самой картинки внутри своего слоя глубины, поэтому предмет переднего плана получает собственные филаменты, а не смешивается с фоном той же яркости. Контраст, «Детали», сглаживание, дизеринг и слияние близких цветов работают поверх глубинного рельефа так же, как поверх яркостного. Переключатель светлое/тёмное выше и режим «по цветам изображения» при этом неактивны (они читают яркость или цвет как высоту, а здесь высоту задаёт глубина); режим каталога катушек и рельеф по глубине взаимоисключающи — включение одного выключает другой.
Нейросеть работает на вашем компьютере. Модель (model_quantized.onnx, 27 МБ,
uint8) и рантайм WebAssembly (14 МБ) вшиты в приложение: интернет не нужен, картинка
никуда не отправляется. Первая оценка занимает несколько секунд (в тестовом прогоне
— около 5 с на картинку 640×480, включая загрузку модели), потом она
переиспользуется: слайдеры, размер и число цветов пересчитываются без повторной
оценки. Оценка идёт в отдельном потоке, интерфейс не замирает. Если модель по
какой-то причине не загрузилась, картинка всё равно обрабатывается по яркости, а
причина показывается в строке статуса.
Что глубина даёт, а что нет. Это порядок «ближе / дальше», а не расстояния: миллиметры задают ваши основание и максимальная высота. Скрытых сторон нейросеть не знает, а ошибиться в плоской или однотонной сцене может — для таких случаев есть «Инвертировать глубину (дальше = выше)»: если сеть прочитала картинку наоборот или нужен эффект врезанного, гравированного изображения. Если на картинке почти нет перепада глубины, приложение так и скажет и в этот раз построит рельеф по яркости — плоская плита с полосами цвета никому не нужна, а выдумывать глубину оно не станет.
Своя карта глубины. Подойдёт PNG: серый, серый+альфа, RGB или RGBA, 8 или 16
бит. 16 бит важны: они сохраняют все уровни, поэтому плавный градиент печатается
плавным склоном, а не террасами (<canvas> браузера сводит PNG к 8 битам, поэтому
читается собственным декодером). По умолчанию светлее — ближе; если у вашей карты
обратное соглашение (у многих инструментов белое — дальнее), включите
«Инвертировать глубину».
Полностью прозрачные пиксели остаются на уровне основания. Карта растягивается под
картинку; если пропорции разные, приложение об этом говорит. Ограничения: файл до
64 МБ, до 16 мегапикселей (4096×4096), без чересстрочной развёртки; в памяти
карта хранится не крупнее 2048 px по длинной стороне (печатная сетка всё равно
мельче). Ошибки (не PNG, повреждён, неподдерживаемый тип, слишком большой)
показываются понятно и не ломают текущий результат. Однотонная карта (нет перепада
значений) не используется — рельеф строится по яркости. Сохранённый проект
несёт копию файла карты (как и саму картинку), поэтому палитра, которая
записана в порядке слоёв глубины, открывается с тем же рельефом; проект с картой
становится больше на размер PNG. Новая картинка сбрасывает карту, а отмена диалога
выбора возвращает на яркость.
Если рельеф проекта восстановить не удалось (карта в файле повреждена, модель
глубины недоступна), проект открывается по яркости с автоматическими цветами и
приложение говорит об этом: накладывать сохранённые цвета на другой порядок слоёв
было бы хуже, чем не накладывать. Экспорт знает, откуда рельеф: Describe.txt
пишет «relief from a depth map», а 3MF получает метаданные ReliefSource и
InvertDepth.
Источник рельефа и «инвертировать глубину» запоминаются в настройках, в проекте и в шагах отмены/повтора; старые проекты без этого поля открываются по яркости. Инверсия не задевает прозрачный фон карты: он остаётся на основании при любом направлении глубины.
Откуда взяты модель и идея. Идея — «относительная глубина как приор» — из навыка
img2threejs, который использует тот же
Depth Anything V2; сам навык (Python, PyTorch) в браузерное приложение не
встраивается. Модель — onnx-community/depth-anything-v2-small (Apache-2.0, файл без
изменений, контрольная сумма записана в
NOTICE.md); среда выполнения —
ONNX Runtime Web (MIT). Тексты лицензий лежат в сборке:
THIRD_PARTY_NOTICES.txt в корне приложения, а уведомления о стороннем коде,
статически собранном в рантайм, — THIRD_PARTY_NOTICES_ONNXRUNTIME.txt (копия из
репозитория ONNX Runtime, тег v1.30.0). Другие размеры модели (Base, Large)
выпущены под CC-BY-NC-4.0 (некоммерческая лицензия) и не включены.
Откуда взять готовую карту: любой инструмент, умеющий сохранить PNG глубины или
высот. Один из вариантов — Unique3D (MIT):
он восстанавливает меш и карты нормалей по одному снимку, а его скрипт
scripts/normal_to_height_map.py превращает карту нормалей в 16-битную карту
высот. Сам конвейер требует видеокарту NVIDIA и гигабайтов весов, поэтому
запускается отдельно, а не внутри HueForge; этот путь мы не прогоняли — берите его
как направление, а не как проверенный рецепт.
Кнопка «Подобрать тон по эталону» в блоке сравнения с эталоном сама находит
контраст и детализацию: приложение измеряет распределение высот эталона тем же
инструментом, которым измеряет свой рельеф (measureRelief в
src/lib/reliefCompare.ts — 16 корзин площади верхней поверхности по её же
диапазону высот), затем прогоняет конвейер по сетке значений «Контраст» и
«Детали» и шагает в сторону самого близкого значения, пока расхождение падает.
Поиск (см. src/lib/toneFit.ts) идёт в воркере, поэтому интерфейс не замирает, а в
подписи кнопки видно «Замер N из M».
Почему сравнимо с чужим STL: профиль нормируется на собственный диапазон высот, поэтому эталон HueForge на 2,16 мм с шагом 0,08 мм сопоставляется с нашей печатью на 11,4 мм с шагом 0,2 мм — важно, куда картинка кладёт массу, а не в миллиметрах. Габарит, шаг сетки и число уровней остаются делом остальных настроек и в оценку не входят.
Правила поиска:
- Текущий тон — тоже вариант. Он измеряется первым и возвращается неизменным, если ни одно значение его не улучшило: подбор никогда не делает хуже того, что уже стоит, а повторное нажатие на подобранном тоне отвечает «текущий тон уже самый близкий» и ничего не сдвигает.
- Меняются только высоты. Цвета, палитра и назначение филаментов пикселям не затрагиваются: кривая применяется к высотам и границам полос одновременно.
- Шаг поиска — 10 %, как у самих ползунков, поэтому найденное значение в них помещается без округления.
- Ограничение области: в режиме «квантование по катушкам» высота идёт от палитры, а не от тонов картинки, поэтому кнопка там выключена и объясняет почему.
Замер на реальном эталоне (match_Front_200x114.stl, HueForge: 200×113,6 мм,
2,16 мм, 983 460 треугольников, 14 уровней) против нашей печати 500×284 мм при
1024 px:
| Состояние | Расхождение распределения высот | Расходятся метрики | Тон |
|---|---|---|---|
| сразу после загрузки (100/100) | 33,8 % | 8 из 10 | — |
| после подбора | 11,7 % | 5 из 10 | контраст 80 %, детали 300 % |
Цена: 34 прогона конвейера — около минуты на картинке 1024 px на этой машине; прогресс показывается в подписи, поиск идёт в воркере. Разница в габарите, шаге сетки и уровнях высот сохраняется: подбор выравнивает тон, а не размер печати.
-
Версионирование семантическое (
vMAJOR.MINOR.PATCH). Единственный источник истины — полеversionвpackage.json; тег релиза обязан с ним совпадать, и workflow релиза отвергает несовпадающие теги. -
Выпуск релиза идёт в порядке «ноты → бамп → CHANGELOG → тег → публикация»: сначала опишите версию в
release-notes/vX.Y.Z.md, затем одной командой получите бампpackage.json/package-lock.json, запись в CHANGELOG до тега и сам тег:VERSION=0.8.4 # замените на выпускаемую версию git pull --rebase origin main # забрать коммит CI о канале прошлого релиза npm run release:prepare -- --version "$VERSION" git push origin main "v$VERSION"
git pullнужен потому, что канал вышедшего релиза дописывает вmainсам CI (последний шаг workflow), — без негоrelease:prepareостановится.Запись обязана попасть в дерево тега:
git show "v$VERSION:CHANGELOG.md"должен её содержать — этого требуетrelease:publish. Подробнее — в DEPLOY.md.Пуш тега
v*запускает workflow Release, и он доводит релиз до конца без ручных шагов: сверяет тег сpackage.jsonи с записью версии вCHANGELOG.md, прогоняет typecheck, тесты и продакшн-сборку на Ubuntu, собирает архивы исходников и deploy-пакета, наwindows-latestсобирает установщик Electron — и затем сам публикует черновик: флаги канала выводятся из тега (включён pre-release, если в теге есть-), тело берётся изrelease-notes/<тег>.md, а еслиreleases/latestоказался бы кандидатом, workflow падает. Последним шагом вCHANGELOG.mdна ветке по умолчанию обновляется канал релиза. -
Локальная публикация (
npm run dist→npm run release:publish) — путь, которым пользуемся, пока счёт GitHub Actions заблокирован: вторая команда сама выводит флаги канала из тега — pre-release дляvX.Y.Z-rc.N, latest для стабильной версии, — заливает установщик, blockmap иlatest.yml, сверяет их со сборкой и падает с ненулевым кодом, еслиreleases/latestуказывает на кандидата — или если в дереве тега нет записи этой версии вCHANGELOG.md. Тело релиза он берёт изrelease-notes/<тег>.mdи сверяет с опубликованным, так что страница релиза и CHANGELOG повторяют один текст.--dry-runпечатает изменения, ничего не выполняя. -
Стабильный канал и pre-release: настольное приложение следит только за latest-релизом репозитория и никогда не подписывается на pre-release (
allowPrerelease = false, каналlatest.yml).releases/latestпропускает pre-release, поэтому каждый RC обязан публиковаться как pre-release GitHub. Флаги конвейер выводит из самого тега (или их ставитrelease:publishв ручном пути); вручную это выглядело бы так:# RC — в теге есть предрелизный суффикс (v0.8.3-rc.5): gh release edit "v$VERSION" --draft=false --prerelease # Стабильная версия — обычный номер (v0.8.3): gh release edit "v$VERSION" --draft=false --latest
Если опубликовать RC как обычный релиз, он станет latest и будет предложен всем стабильным установкам (так однажды и вышло с
v0.8.3-rc.4; сейчас он снова pre-release). Установки RC получают следующий стабильный релиз, когда он выходит, а стабильные установки RC не видят никогда. -
install.batпри каждом запуске сравнивает локальную версию (изpackage.json) с последним релизом GitHub и предлагает скачать и применить обновление на месте (собственно вызовы API делаетupdate.ps1). Перекачивать архив для обновления не нужно — кроме случаев, когда меняются самиinstall.bat/start.bat/update.ps1: тогда требуется свежая распаковка. -
Ноты релизов живут в репозитории: CHANGELOG.md повторяет все релизы — версия, дата, канал и полный текст нот — чтобы историю можно было читать из кода, а не только со страницы релиза. Ноты будущей версии пишутся заранее в
release-notes/<тег>.md(правила каталога), поэтому запись появляется в CHANGELOG до тега и остаётся в дереве самого релиза.npm run changelog:syncперегенерирует файл из релизов GitHub (нужен авторизованныйgh); после публикации он лишь заменяет «Ожидает тега» на канал релиза, а версии без страницы релиза берёт изrelease-notes/.
Тёмная / Светлая / Nord / Solar — переключатель в верхней панели, выбор сохраняется.
npm install # один раз
npm run dev # dev-сервер на http://127.0.0.1:5173
npm test # 523 теста (48 файлов); тест на настоящей модели глубины идёт ~8 с
npm run build # продакшн-сборка в dist/- Всё считается локально в браузере — обработки на сервере нет, изображения
не покидают ПК. Это относится и к нейросети глубины: модель вшита в сборку, а
политика безопасности страницы (
connect-src 'self') не пускает запросы наружу — это закреплено тестом (src/test/csp.test.ts). - Перед обработкой изображение приводится к рабочему разрешению: до 1024 px по большой стороне (это привязка к соплу — примерно ячейка на сопло 0,4 мм), а превью «Исходное» декодируется отдельно до 2048 px, чтобы мелкие детали были видны на карточке.
- Рельеф — высотная сетка с общими вершинами соседних ячеек: перепад высот между соседями становится диагональной гранью, вертикальные стенки идут только по внешнему контуру, дно — плоская плита. Сетка водонепроницаемая (проверяется тестами), поэтому режется без ошибок.
Все документы репозитория ведутся на русском языке (команды, имена файлов и фрагменты кода — как есть). Текущий состав:
- README.md — этот файл: установка, использование, релизы.
- DEPLOY.md — вариант «сервер + браузер» и публикация релизов.
- CHANGELOG.md — история версий из релизов GitHub.
- public/THIRD_PARTY_NOTICES.txt, public/THIRD_PARTY_NOTICES_ONNXRUNTIME.txt и src/assets/depth-anything-v2-small/NOTICE.md — лицензии и происхождение вшитой модели глубины и рантайма.
- docs/hueforge-classic-prompt.md — описание классического алгоритма HueForge для сверки.