176 lines
22 KiB
Markdown
176 lines
22 KiB
Markdown
# Надписи и рельеф — точная подгонка и отделка
|
||
|
||
Дополнение к разделу «Надписи, логотипы и рельеф» SKILL.md: выбор маршрута для внешнего контура,
|
||
перевод SVG-логотипа в примитивы эскиза, точная подгонка надписи (кегль, ширина, кайма, стыковка
|
||
блоков) и отделка рельефа. Все числа получены пробами на конкретных начертаниях (Lobster, Bevan,
|
||
Zilla Slab): это порядок величины и форма зависимости, а не константы — свои значения перезамеряй
|
||
на своём шрифте, `widthFactor` и `thinThickness`.
|
||
|
||
## Внешний контур: не диктуй координаты, если фигуру нельзя описать формулой
|
||
|
||
Три маршрута, и выбор между ними механический — не по вкусу, а по происхождению геометрии.
|
||
|
||
| Откуда фигура | Чем строить |
|
||
|---|---|
|
||
| **Вычисляется**: пластины, рёбра, сетка отверстий | `sketch_create(entities[])` — прямо в вызове, размеры вяжи переменными |
|
||
| **Срисована**: логотип, шаблон, кулачок, профиль | внешний вектор → JSON → `sketch_create(entitiesFile=…)` |
|
||
| **Пришла в CAD-формате**: файл от смежника | `import_drawing` (.dxf/.dwg) → фрагмент → `fragment_place` |
|
||
|
||
**Обводка занимает сотни примитивов, и переписывать их в тело вызова незачем.** У `sketch_create`,
|
||
`sketch_add` и `fragment_create` есть `entitiesFile` — путь к JSON того же формата, что `entities`;
|
||
сервер читает файл сам. Замер на эмблеме Volvo: SVG в 1.5 КБ даёт 96 примитивов (50 отрезков +
|
||
46 дуг) и 9.5 КБ JSON; через `entitiesFile` это один вызов на одну строку аргументов, а эскиз
|
||
строится за один заход. Оба параметра можно задать вместе — сначала `entities`, следом файл:
|
||
так к готовому контуру дописывают рамку или ось, не трогая файл.
|
||
|
||
Что проверить **до** постройки, а не после: габарит в ответе (`fragment_create` печатает
|
||
фактический — сверь с задуманным) и вложенные контуры. Правил заливки у эскиза КОМПАС нет: любой
|
||
вложенный контур он режет как отверстие, даже там, где вектор заливал материал. Конвертер такие
|
||
места называет заранее — на них смотри до выдавливания, иначе дыры обнаружатся на снимке.
|
||
|
||
## Логотип, которого нет в шрифте: SVG → примитивы эскиза
|
||
|
||
Переводи готовый вектор в примитивы эскиза сам — `line` и `arc3points`, а не ломаной: контур из
|
||
сотен звеньев даёт деталь в сотни граней, и фаска по такому низу не строится. Кубические Безье режь
|
||
пополам, пока дуга по трём точкам не ляжет в допуск. Четыре вещи, каждая из которых стоила захода:
|
||
|
||
- **Ось Y в SVG смотрит вниз** — без инверсии контур встаёт вверх ногами, и это видно только на снимке.
|
||
- **Дуги задавай тремя точками** (`arc3points`): концы заданы явно, соседние примитивы стыкуются
|
||
точно. Перед постройкой проверь цепочку — конец примитива против начала следующего, разрыв 0.
|
||
- **Чисти мелочь под масштаб детали — параметром `minSegment`.** SVG рисуют в своём габарите (сотня
|
||
условных единиц), и его скругления после масштабирования превращаются в дуги по 0.02–0.5 мм;
|
||
замыкающий `z` часто даёт ещё и сегмент нулевой длины. `chamfer_edge` по такому набору отвечает
|
||
«катет больше длины N из M рёбер» — и это правда. Передай `sketch_create(minSegment=катет)` (тот же
|
||
параметр есть у `sketch_update` и `fragment_create`), и та же фаска пройдёт с первой попытки;
|
||
отчёт о выброшенном читай, а не пролистывай — `help(topic="contour-cleanup")`.
|
||
|
||
**Порядок для фаски по контуру логотипа — только такой, и он неочевиден:**
|
||
|
||
1. **чистка** порогом ≈ будущего катета (`minSegment`);
|
||
2. **замер ЧИЩЕНОГО силуэта** — `sketch_measure_thickness(entitiesFile, minSegment=тот же порог)`;
|
||
3. **катет по узкому месту** чищеного силуэта: `c ≤ (узкое_место − 2·w)/2`, где `w` — ширина нити.
|
||
|
||
Менять шаги местами нельзя: **чистка СУЖАЕТ узкое место**. Она заменяет скругление острым углом —
|
||
в выпуклом углу это добавляет материал (габарит подрастает), а в вогнутом срезает его, и именно
|
||
вогнутые углы образуют перемычки между штрихами. Сужение доходит до единиц процентов узкого места —
|
||
этого хватает, чтобы катет, подобранный по нечищеному контуру, перестал проходить свой же критерий.
|
||
Числа снимай на СВОЁМ контуре: они принадлежат рисунку, а не приёму.
|
||
|
||
- **Ширину штриха и узкое место меряй инструментом, а не глазом** — `sketch_measure_thickness`
|
||
ничего не строит и документа не требует, поэтому зовётся ДО эскиза
|
||
(`help(topic="silhouette-thickness")`). От этих чисел зависит, останется ли что-то от рисунка
|
||
после фаски и напечатается ли он вообще; шаг растра в ответе — точность округления катета.
|
||
|
||
## Подгонка ширины и кегля
|
||
|
||
- **`widthFactor` меняет пропорцию крупно, `height` — точно.** Замер (Lobster «OldMan», h=17):
|
||
wf 1.00 → 71.84 мм, wf 1.25 → 92.83 (+29 %) — то есть это настоящий масштаб ширины, и без него
|
||
скриптовый шрифт в вытянутое поле не вписать. Но КОМПАС **квантует его ступенями**: wf 1.25,
|
||
1.26, 1.27, 1.28 дают ячейку 100.64 и ширину 92.83…93.14 (+0.3 %), а на 1.29 ячейка прыгает на
|
||
103.34 и ширина сразу 95.49 (+2.5 %). Отсюда правило: пропорцию задаёшь `widthFactor`, точную
|
||
ширину добираешь `height` — подгонка через wf топчется на ступени.
|
||
- **`height` двигает ширину плавно, а высоту — ступенями.** Замер (Lobster «OldMan», wf 1.18):
|
||
h 17.00 → глифы 85.37 × **17.07**; h 16.94 → 85.11 × **17.07** (ширина отозвалась, высота нет);
|
||
h 16.50 → 82.90 × 16.54; h 15.50 → 77.95 × 15.60. Значит подгонять ширину кеглем можно, но
|
||
**вертикаль по нему не считается**: после пересчёта перечитай фактический габарит из ответа и
|
||
привязывай верх/низ блока ТОЧКОЙ ВСТАВКИ, а не кеглем.
|
||
- **Кегль под заданный габарит считается за ОДНУ пробу, а не подбирается.** При фиксированном
|
||
`widthFactor` прирост каймы Δ (сумма по обеим сторонам) от кегля не зависит — фигура подобна.
|
||
Значит: построил пробу с любым `height₀`, замерил ширину глифов `W₀` и суммарный прирост Δ,
|
||
дальше `height = height₀ · (W_цель − Δ) / W₀` — и целевой габарит получается с первого раза.
|
||
**Δ бери только из СВОЕЙ пробы — на том же шрифте, `widthFactor` и `thinThickness`.** Числа из
|
||
этого файла (Lobster: Δ=4.62 при t=1.5/wf 1.29; Δ=4.93 при t=1.6/wf 1.18; Δ=2.92 при t=0.9/wf 1.33)
|
||
приведены как порядок величины, а не как константа: подставив чужое Δ, промахнёшься на проценты
|
||
по ширине — а именно её ты и подгоняешь.
|
||
- **Шаг второй, без которого формула обманывает: проверь ВЫСОТУ, которая из неё вышла.** Кегль
|
||
подобран по ширине, но вместе с ним задалась и высота блока букв — а от неё зависит, дотянется ли
|
||
кайма до плашки. Считай до построения: `низ каймы = низ глифов − t`, и он обязан зайти на плашку.
|
||
Замер (Lobster «OldMan», целевая ширина 90, кайма t=1.5): при `wf 1.29` формула даёт ровно
|
||
90.00 по X и глифы высотой 15.2 → низ каймы 11.77 против верха плашки 10.5, **разрыв 1.27 мм**,
|
||
деталь распалась бы на два тела; при `wf 1.18` — глифы 17.07 → низ каймы 9.93, **перекрытие
|
||
0.57 мм**, одно тело. Отсюда рабочее правило: **`widthFactor` — ручка не пропорции, а стыковки.**
|
||
При фиксированной целевой ширине больший `wf` даёт более низкий блок букв, и вертикаль
|
||
проигрывается: сначала подбери `wf` так, чтобы высота блока перекрыла плашку, и только потом
|
||
добирай точную ширину кеглем.
|
||
|
||
## Кайма (тонкая стенка вокруг букв)
|
||
|
||
- **Кайма на острых терминалах шире `thinThickness` — и по каждой стороне по-своему.** Стенка
|
||
`outward` обходит контур с миттером: на остром окончании глифа габарит растёт **на сторону** на
|
||
`t / sin(θ/2)`, а не на `t` (Lobster: слева +0.90 при t=0.9, справа +1.70 — терминал «n»).
|
||
Величина принадлежит не шрифту, а КОНКРЕТНОМУ начертанию и толщине стенки, поэтому у каждого
|
||
числа держи в уме пару (t, wf): Lobster «OldMan» при t=0.9 дал слева +0.90 / справа +1.70 при
|
||
wf 1.18 и +0.90 / +2.09 при wf 1.40; при t=1.5 — +1.50 / +3.12. Общее в них одно: **сбоку
|
||
прирост равен t, а на остром терминале — t/sin(θ/2)**, и переносить абсолютные числа между
|
||
вариантами нельзя — перезамеряй после смены `widthFactor` или `thinThickness`.
|
||
- **По вертикали кайма прибавляет ровно 2·t.** Сверху и снизу глифов миттера нет (касательные
|
||
ровные), поэтому высота блока = высота глифов + 2·t считается точно, без пробы; замером это
|
||
подтверждается на всех вариантах (t=0.9 → +1.80). Мерить нужно только ширину.
|
||
- **`align`/`vAlign` центруют ГЛИФЫ, а кайма несимметрична — значит центр надо считать.** Просьба
|
||
«поставить надпись по центру поля 90 мм» через `align="center", x=45` даёт силуэт, съехавший на
|
||
полразницы приростов: у Lobster при t=1.5 слева +1.50, справа +3.12. Целься не в центр поля, а в
|
||
тот X, при котором СИЛУЭТ ляжет в поле: `x = (Δ_лев + (L − Δ_прав)) / 2`. Замер: (1.50 + 90 −
|
||
3.12)/2 = 44.19 → глифы 1.50…86.88, силуэт с каймой ровно 0…90.00. То же и по вертикали, только
|
||
там прирост симметричен (t сверху и снизу), поэтому `vAlign="top"` в `верх поля − t` попадает без
|
||
поправки.
|
||
- **Толщина каймы — ещё и параметр стыковки блоков.** Кайма опускает силуэт надписи ниже её
|
||
глифов на `t`, и именно это перекрытие с плашкой держит деталь одним телом. Считай так: низ
|
||
каймы = низ глифов − t, и он должен зайти на плашку хотя бы на несколько десятых. Проверка —
|
||
сводка «Тел:» в ответе самого `primitive(union)`: у примитивов она надёжна, потому что сервер
|
||
сам сверяет объём до и после и отвергает объединение, ничего не давшее. **У `loft`/`sweep` этой
|
||
сводке верить нельзя** — там она однажды напечатала «Тел: 1», когда тел было два (см. основной
|
||
навык, п. «Ответ мутирующей операции»); в сомнительном случае считай тела
|
||
`describe_model(sections="bodies")`.
|
||
- **Кайма вокруг ПРЯМОУГОЛЬНОЙ плашки — вторым примитивом, а не тонкой стенкой.** Стенка обходит
|
||
контуры эскиза, а плашка по playbook'у строится `primitive`, и каймы у неё не появится. Клади под
|
||
неё второй блок на `2·t` больше и на высоту подложки (85 × 13.5 × 2 под плашкой 82 × 10.5 × 3) —
|
||
получается тот же кант. Сводить прямоугольник в один эскиз с буквами не надо: контуры пересекутся,
|
||
и эскиз станет невалидным.
|
||
|
||
## Разрядка (трекинг)
|
||
|
||
- **Разрядки (трекинга) у примитива нет — её набирают пробелами.** Ширину пробела для конкретного
|
||
шрифта и кегля вычисляют двумя пробами: «EDITION» → 38.38 мм, «E D I T I O N» → 47.11 мм,
|
||
значит пробел 1.455 мм (Bevan, cap 5.5).
|
||
|
||
## Отделка рельефа и соседняя геометрия
|
||
|
||
- **Пакетом по контуру рельефа не строится СКРУГЛЕНИЕ — ни сверху, ни на малом радиусе; фаска
|
||
строится по обоим контурам.** Скругление: на Zilla Slab «AB» (203 ребра) одиночное верхнее ребро
|
||
берёт R0.1 и R0.2, а `fillet_edge` сразу по всем отказывает при обоих радиусах — соседние
|
||
скругления конфликтуют; уменьшение радиуса не спасает (Lobster, 135 верхних рёбер: отказ и при
|
||
R0.3, и при R0.15). Фаска: `chamfer_edge(edgeIndices=[…])` проходит с первой попытки и по низу
|
||
(0.3 на 59 и на 120 рёбрах), и по ВЕРХУ рельефа (0.2 на 135 рёбрах букв, отдельно 0.2 на 133
|
||
рёбрах канта плашки с надписью) — прямые, NURBS, дуги и огрызки 0.03 мм вперемешку. Значит
|
||
притупление кромок по всему силуэту, хоть снизу против «слоновьей ноги», хоть сверху по
|
||
требованию чертежа, — это ОДНА операция, и делается она фаской. Рёбра берутся окном:
|
||
`list_edges(minZ=-0.01, maxZ=0.01)` — низ, `list_edges(minZ=h-0.01, maxZ=h+0.01)` — верх (сузить
|
||
до нужной части силуэта помогает `minY`/`maxY`). **Порядок «сначала скругления углов, потом фаска»
|
||
работает** — дуги от R0.5 фаске не мешают. В отборе встречается тип `other` (у сложных кривых) —
|
||
он тоже принимается параметром `type`.
|
||
- **Отказ фаски на НЕЧИЩЕНОМ контуре — не повод уменьшать катет: успех немонотонен по размеру.**
|
||
На контуре с острыми клиньями между элементами `chamfer_edge` отказывает на нескольких катетах
|
||
подряд и проходит на большем — проверено подряд, с `rebuild` между попытками и на неизменных
|
||
индексах рёбер. Похоже, малый катет оставляет в остром клине вырожденный «нос», который КОМПАС
|
||
сшить не может, а большой срезает остриё целиком. **Но перебирать катет не надо**: правильное
|
||
лечение — `minSegment` (см. SKILL.md §«Надписи, логотипы и рельеф»). После чистки совет «попробуй
|
||
увеличить» становится вреден: рабочий диапазон катета сдвигается ВНИЗ вместе с узким местом, и
|
||
катет, проходивший на нечищеном контуре, чищеный отвергает. Не переносите подобранное значение
|
||
между состояниями контура — оно к ним привязано.
|
||
Прежняя формулировка добавляла «исключение самых коротких рёбер картину не меняет, то есть дело
|
||
не в них» — вывод неверен: исключённое из `edgeIndices` ребро остаётся в теле, и это не то же
|
||
самое, что убрать сегмент из контура. Убранный из контура — меняет.
|
||
- **`primitive(subtract)` режет ВСЁ, что попало в его габарит — включая уже построенные буквы.**
|
||
Сверять надо **глифы, а не кайму**, и только те, что дотягиваются до кармана ПО ВЫСОТЕ: карман
|
||
обычно вычитается от z основания вверх (например от 2 до 3), а кайма ростом всего 2 мм в этот
|
||
слой не попадает — она может сколько угодно перекрывать плашку в плане, и именно это перекрытие
|
||
держит деталь одним телом. Опасны буквы: они идут на всю высоту 3 мм, поэтому их нижняя кромка
|
||
по Y должна быть выше верхней кромки кармана. Проверка постфактум — **точное равенство убыли
|
||
объёма** расчётному (79 × 7.5 × 1.0 = 592.5 против фактических 592.5 мм³): объём приходит в
|
||
сводке ответа самой операции, любое касание соседней геометрии сразу ломает равенство, а
|
||
габарит и снимок его не покажут.
|
||
- **Углы вокруг надписи — одним `fillet_edge(points=[…])`.** На теле с сотнями рёбер список из
|
||
`list_edges` неподъёмен, но точки углов известны из построения: передай их списком, и все рёбра
|
||
уйдут в ОДНУ операцию дерева. Если ребра в части точек нет (типичный случай — угол «съеден»
|
||
каймой букв), ошибка перечислит ВСЕ такие точки сразу: убери их и повтори одним вызовом.
|