Модель не влезает на стол: почему плоскость — плохой нож
- вторник, 25 августа 2026 г. в 00:00:06
У меня браузерный генератор вывесок: человек набирает надпись, получает STL-модель корпуса под светодиодную ленту. Всё считается на его же машине, файл никуда не уходит. Однажды пришёл вопрос, который сломал мне неделю: надпись 500 мм, стол принтера 256×256. Что делать?
Стандартный ответ — «режьте в слайсере». Слайсер режет плоскостью, и это честно работает ровно до момента, когда вы забираете со стола две половинки со срезами и понимаете, что совместить их нормально не получится они не совпадают. Клей на PLA держит на честном слове и еще и срезы не ровные, при склейке половинки едут, и на видном месте остаётся ступенька.
Я решил сделать рез внутри генератора: чтобы части сразу выходили с замками и собирались без клея. Дальше — про то, как я трижды всё переделал, и про шесть граблей, каждая из которых стоила мне вечера.
Первое, что приходит в голову — вертикальная плоскость: всё левее неё в одну часть, всё правее в другую. Пятнадцать строк кода, работает.
Ровно до первой рукописной надписи. Плоскость режет всё, чего касается, — и если она задела заглавную «N» краем, буква разваливается пополам. При этом рядом, между буквами, есть просвет, где резать вообще нечего: там пусто.
Человек, который смотрит на превью, видит это мгновенно и говорит: «нож цепляет N, хотя мог обойти». И он прав.
Если сформулировать задачу словами, получается: провести линию сверху вниз так, чтобы она как можно меньше шла по материалу. Это ровно поиск кратчайшего пути на сетке, только «расстояние» — это не длина, а сколько материала мы режем.
Беру силуэт модели в плане, растеризую с шагом 2 мм и раскладываю динамикой:
// Рез как поиск дешёвого пути: материал дорогой, пустота дешёвая const cost = ys.map(y => xs.map(x => inMat(rings, x, y) ? 400 : 1)); for (let j = 1; j < ys.length; j++) { for (let i = 0; i < W; i++) { for (const d of [-1, 0, 1]) { // вниз: прямо или наискось const k = i + d; if (k < 0 || k >= W) continue; const c = dp[j - 1][k] + cost[j][i] + (d ? 0.4 : 0); if (c < dp[j][i]) { dp[j][i] = c; from[j][i] = k; } } } }
Три числа, каждое из которых я потрогал руками:

Материал стоит 400, пустота — 1. При соотношении вроде 10 линия соглашалась резать букву, чтобы срезать угол. С четырьмястами она лезет в материал, только если обхода нет вовсе.
Шаг растра 2 мм. Сначала было 3 — и нож не видел просветы шириной ровно три миллиметра, то есть резал буквы там, где был проход. Уменьшил — и он нашёл дорогу между буквами, которую до этого «не замечал».
Боковой шаг штрафуется на 0.4. Без штрафа путь виляет по пустоте, потому что все пустые клетки для него одинаковые, и рез получается диагональным зигзагом. Со штрафом линия идёт прямо, пока не появится причина свернуть.
Итог на моей тестовой надписи: линия перестала быть прямой и теперь ищет, где пересечь материал подешевле. На этом кадре она проходит по тонкой связке между «N» и «e» — заглавная буква остаётся целой, а режется перемычка толщиной в пару миллиметров. Прямая плоскость на том же месте прошла бы через середину штриха.
Обойти материал совсем удаётся не всегда: если надпись плотная и её надо разложить на три куска, сплошного просвета сверху донизу может не найтись. Тогда алгоритм выбирает самое узкое пересечение из возможных — и в этом месте ставит замок.
Там, где путь всё-таки пересёк материал, нужен замок. Я сделал три варианта и выбираю по тому, что физически влезает.

Ласточкин хвост в теле. Трапеция: узкая часть на шве, широкая уходит внутрь. Размер не константа, а доля от локальной толщины сечения — примерно 0.28. Константа плоха тем, что на тонком месте хвост прорезает стенку насквозь, а на толстом выглядит игрушечным. Снаружи шва не видно ничего.
Вставка-бабочка с тыла. Когда на хвост не хватает материала: в задней грани через шов фрезеруется карман, к нему печатается отдельная пластина-бабочка. С лица по-прежнему чисто.
Ничего. У круглых неоновых трубок прижимать нечего физически: сечение касается плоскости линией. Там части держит задняя панель, и генератор об этом честно пишет, вместо того чтобы обещать несуществующую защёлку.
Перед тем как врезать замок, я проверяю его объёмом: пересекаю тело будущего шипа с обеими частями и смотрю, что он реально сидит в материале и слева, и справа. Не прошло — сдвигаюсь вдоль линии реза и пробую следующее место. Именно эта проверка избавила от «шипов-обрубков», торчащих в воздухе.
Контур режущей призмы обязан идти против часовой стрелки. Библиотека геометрии читает контур в обратном обходе как дырку. Призма получается пустой, и рез просто не происходит: ни ошибки, ни исключения — модель остаётся целой, как будто ты ничего не нажимал. Полчаса я искал ошибку в логике, а надо было посмотреть на знак площади.
Путь нельзя прореживать на выдаче. Я возвращал каждую вторую точку, чтобы превью рисовалось легче. После двух-трёх правок подряд кривая вырождалась в грубую ломаную — «точки пропадают». Данные для пересчёта и данные для показа — разные вещи, и прореживать можно только вторые.
Плоскость перетаскивания строится через сам взятый объект. Я строил её через центр всей линии реза — и на наклонном ракурсе узел улетал от движения мыши на пару пикселей. Это не «дрожащая мышь», это параллакс: чем дальше точка от плоскости, тем сильнее промах.
Осколки надо пришивать с обеих сторон. Линия, дважды пересёкшая завиток буквы, отрезает её верхушку крошечным островком. Я приваривал такие островки из левой части в правую — и ровно такой же обрубок продолжал жить в правой. Смешно, но заметил не я.
Жёсткий бамп в жёстком пазу не защёлкивается. В первой версии на торце шипа был шарик, а в пазу лунка — «щёлкнет и зафиксируется». Не щёлкает: пластику некуда деформироваться, шип просто не входит. Убрал шарик: в плане деталь держит сам хвост, по вертикали — крышка корпуса.
Шип должен расти от стола, а паз быть открыт доверху. Иначе внутри замка появляется нависание, слайсер лепит туда поддержки, и вычистить их из паза невозможно. А козырёк в 1.2 мм над пазом я не заметил вовсе — шип упирался в него и не заходил, хотя по чертежу всё сходилось.
Автомат ошибается на нестандартных надписях, поэтому линию реза можно править мышью: она показана лентой по фактическому пути, на ней узлы, каждый тянется вбок. Замок ставится кликом, убирается правой кнопкой.
Отдельно скажу про честность отрисовки. Сначала я рисовал линию реза прямой палкой на средней координате — так проще. Человек смотрел на превью и видел, что нож проходит сквозь букву, хотя настоящий рез её обходил. Он не поверил инструменту, и был прав: показывать надо то, что произойдёт, а не упрощённую схему того, что задумано.
Модель 500 × 197 × 24 мм режется на три части, каждая влезает в 256×256, собирается без клея, шов снаружи чистый. Живой посадки я пока не проверял на всех вариантах — зазор 0.25 мм подобран на пробных купонах, и это то место, где теория заканчивается и начинается конкретный принтер.
Всё описанное работает в бесплатном инструменте, ссылка в профиле.