<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Артур Фаттахов</title><subtitle>Я Артур, дизайнер  с 14-летним опытом и кейсами для Альфа-Банка, МТС, Роснефти, СБИС, Т2 и Фармленда. Автоматизирую рутину</subtitle><author><name>Артур Фаттахов</name></author><id>https://teletype.in/atom/rhv2006</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/rhv2006?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@rhv2006?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=rhv2006"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/rhv2006?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-10-19T12:24:14.286Z</updated><entry><id>rhv2006:850dZpOdUEY</id><link rel="alternate" type="text/html" href="https://teletype.in/@rhv2006/850dZpOdUEY?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=rhv2006"></link><title>Бот, который откликается на вакансии за меня</title><published>2026-08-19T08:53:49.005Z</published><updated>2026-08-19T08:53:49.005Z</updated><summary type="html">После истории со скриптом, который вылавливал вакансии в телеграм-каналах, я подумал: а почему бы не пойти дальше? На hh.ru та же самая проблема, только в других декорациях — свежие вакансии по 3D и моушену появляются, откликов набирается уже полсотни в первые полчаса, и если ты не в числе первых десяти, шансы просесть очень сильно. Ручками сидеть и обновлять поиск каждые пять минут — это не жизнь. Так родился второй скрипт, который уже не просто присылает уведомление, а сам заходит на сайт, читает вакансию, решает, стоит ли откликаться, и если да — откликается вместо меня, с сопроводительным письмом.</summary><content type="html">
  &lt;p id=&quot;3Zm5&quot;&gt;После истории со скриптом, который вылавливал вакансии в телеграм-каналах, я подумал: а почему бы не пойти дальше? На hh.ru та же самая проблема, только в других декорациях — свежие вакансии по 3D и моушену появляются, откликов набирается уже полсотни в первые полчаса, и если ты не в числе первых десяти, шансы просесть очень сильно. Ручками сидеть и обновлять поиск каждые пять минут — это не жизнь. Так родился второй скрипт, который уже не просто присылает уведомление, а сам заходит на сайт, читает вакансию, решает, стоит ли откликаться, и если да — откликается вместо меня, с сопроводительным письмом.&lt;/p&gt;
  &lt;h2 id=&quot;nNho&quot;&gt;Из чего это собрано&lt;/h2&gt;
  &lt;p id=&quot;VB9I&quot;&gt;В основе — Playwright, библиотека для управления браузером из кода. По сути это тот же браузер, что открывается у тебя на компьютере, только им управляет скрипт: он умеет кликать, скроллить, читать текст со страницы, заполнять формы. Я специально запускаю его не в headless-режиме, а с видимым окном — так проще заметить, если что-то пошло не так, и вручную вмешаться.&lt;/p&gt;
  &lt;p id=&quot;vGID&quot;&gt;Для распознавания «подходит вакансия или нет» используется та же связка, что и в телеграм-версии — локальная модель через Ollama. Ключевые слова и правила фильтрации вынесены в отдельный файл &lt;code&gt;filters.txt&lt;/code&gt;, чтобы не лезть в код каждый раз, когда нужно подправить список профессий. Модель получает текст вакансии и по инструкции решает: да, подходит, вот название должности — или нет.&lt;/p&gt;
  &lt;h2 id=&quot;XL4m&quot;&gt;Как проходит один цикл&lt;/h2&gt;
  &lt;p id=&quot;30pE&quot;&gt;Скрипт открывает поиск на hh.ru с готовыми параметрами — по сути, обычная страница результатов, только с сортировкой по свежести и периодом в один день. Дальше он прокручивает страницу вниз несколько раз, чтобы подгрузились все вакансии, и собирает ссылки на каждую карточку.&lt;/p&gt;
  &lt;p id=&quot;J4Tf&quot;&gt;По каждой ссылке скрипт заходит на страницу вакансии по очереди. Первым делом проверяет, не откликался ли уже на эту вакансию раньше — для этого ведётся простой текстовый журнал &lt;code&gt;applied_links.log&lt;/code&gt;, куда пишутся уже пройденные ссылки без параметров запроса (чтобы одна и та же вакансия с разными UTM-метками не считалась дважды).&lt;/p&gt;
  &lt;p id=&quot;uAGT&quot;&gt;Дальше идёт проверка на «вакансию с тестом» — на hh.ru часто вместо обычного отклика просят сначала пройти тестовое задание или ответить на вопросы прямо на странице. Такие вакансии скрипт узнаёт по характерным фразам в описании вроде «пройти тест» или «заполните анкету», а также по слову question в самой ссылке. Если это тестовая вакансия — скрипт помечает её как обработанную, но вкладку намеренно не закрывает, чтобы я потом мог сам вручную пройти тест, если вакансия того стоит. Автоматизировать прохождение произвольных тестов — идея так себе, там слишком много нестандартного.&lt;/p&gt;
  &lt;p id=&quot;LL8W&quot;&gt;Если тестов нет, весь текст страницы отправляется в модель на вердикт. Если ответ отрицательный, вакансия просто помечается пройденной и скрипт идёт дальше. Если положительный — начинается самое интересное.&lt;/p&gt;
  &lt;h2 id=&quot;c3vE&quot;&gt;Автоматический отклик&lt;/h2&gt;
  &lt;p id=&quot;Qrx8&quot;&gt;Скрипт ищет кнопку отклика на странице, кликает по ней, и дальше по цепочке разбирает возможные всплывающие окна: предупреждение про переезд в другой город («Всё равно откликнуться»), выбор резюме из списка, если их несколько, поле для сопроводительного письма. Письмо берётся не из головы модели, а из заранее заготовленных шаблонов в файле &lt;code&gt;templates.txt&lt;/code&gt; — они чередуются по кругу через &lt;code&gt;itertools.cycle&lt;/code&gt;, чтобы не заваливать разных работодателей одинаковым текстом слово в слово. После заполнения скрипт нажимает «Отправить» и записывает результат.&lt;/p&gt;
  &lt;p id=&quot;287y&quot;&gt;Каждый успешный отклик попадает в Excel-таблицу — через &lt;code&gt;pandas&lt;/code&gt; дописывается строка с названием вакансии, компанией, ссылкой, статусом и датой. Получается что-то вроде личного трекера откликов, который ведётся сам, без единой лишней вкладки Google Sheets.&lt;/p&gt;
  &lt;h2 id=&quot;vYw8&quot;&gt;Мелочи, которые оказались важны на практике&lt;/h2&gt;
  &lt;p id=&quot;psw2&quot;&gt;Часть решений в коде — это не архитектурные изыски, а просто следы набитых шишек. Например, отдельно оговорено, что текст вакансии для проверки на «тест» берётся именно из блока описания, а не со всей страницы целиком — потому что в шапке или подвале сайта тоже может случайно встретиться слово вроде «тестирование», и тогда скрипт зря пропускал бы нормальную вакансию. Ещё один нюанс — переменные окружения &lt;code&gt;NO_PROXY&lt;/code&gt; и &lt;code&gt;all_proxy&lt;/code&gt; выставляются в начале файла, чтобы запросы к локальной Ollama не улетали через прокси, который используется для остального трафика.&lt;/p&gt;
  &lt;p id=&quot;iRGA&quot;&gt;В конце работы, после того как список вакансий пройден, скрипт не завершается сразу — он ждёт, пока я не закрою вручную все оставшиеся вкладки с тестовыми заданиями. Это осознанный компромисс: полностью бросать открытые вкладки не хочется, а автоматически их закрывать — значит потерять те вакансии, которые могли бы того стоить.&lt;/p&gt;
  &lt;p id=&quot;Fujd&quot;&gt;В сумме получилось не идеальное, но вполне рабочее решение: я просыпаюсь, а часть вакансий, на которые стоило откликнуться, уже обработана, письмо отправлено, запись в таблице есть, и в браузере меня ждут только те случаи, где действительно нужна моя голова, а не набор правил.&lt;/p&gt;

</content></entry><entry><id>rhv2006:ZN7TeEHCqY3</id><link rel="alternate" type="text/html" href="https://teletype.in/@rhv2006/ZN7TeEHCqY3?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=rhv2006"></link><title>Как я написал бота, который ищет мне вакансии, пока я сплю</title><published>2026-08-19T08:49:53.977Z</published><updated>2026-08-19T09:18:18.478Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/b3/3c/b33c5afa-37bb-4bac-a96b-37b007e5c77f.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/2f/4b/2f4bb919-39a0-4e42-b0a3-0019c11f04e7.jpeg&quot;&gt;Есть такая штука в жизни 3D-художника и моушен-дизайнера: хорошие заказы разлетаются за пятнадцать минут после публикации. Пока ты долистываешь ленту в метро, вакансия уже занята кем-то, кто сидел в чате и обновлял канал каждые полминуты. Раньше я и сам так делал — держал открытыми штук двадцать телеграм-каналов с вакансиями и время от времени листал их вручную. Занятие такое себе: скучно, отнимает время и всё равно что-то пропускаешь.</summary><content type="html">
  &lt;p id=&quot;PGis&quot;&gt;Есть такая штука в жизни 3D-художника и моушен-дизайнера: хорошие заказы разлетаются за пятнадцать минут после публикации. Пока ты долистываешь ленту в метро, вакансия уже занята кем-то, кто сидел в чате и обновлял канал каждые полминуты. Раньше я и сам так делал — держал открытыми штук двадцать телеграм-каналов с вакансиями и время от времени листал их вручную. Занятие такое себе: скучно, отнимает время и всё равно что-то пропускаешь.&lt;/p&gt;
  &lt;p id=&quot;LbmX&quot;&gt;В какой-то момент я решил, что раз я умею писать код, глупо продолжать заниматься этим руками. Так появился скрипт, который я в шутку назвал &lt;code&gt;stalker.py&lt;/code&gt; — он действительно «выслеживает» вакансии по каналам, только не человека, а посты нужного профиля.&lt;/p&gt;
  &lt;h2 id=&quot;LhIb&quot;&gt;Что он вообще делает&lt;/h2&gt;
  &lt;p id=&quot;QUil&quot;&gt;Логика простая, если разложить по шагам. Скрипт подключается к моему телеграм-аккаунту через Pyrogram — это библиотека, которая позволяет работать с Telegram API как с обычным пользователем, а не через бота. Разница важна: боту нельзя просто так читать историю каналов, на которые он не подписан от имени владельца, а через юзер-аккаунт — можно, если ты сам туда вступил.&lt;/p&gt;
  &lt;p id=&quot;6cpw&quot;&gt;Дальше он проходит по списку каналов из файла &lt;code&gt;channels.txt&lt;/code&gt; и смотрит последние сообщения. Есть два режима глубины поиска: если скрипт запустился утром, между 11:40 и 11:59, он сканирует последние 15 часов — то есть подчищает всё, что накопилось за ночь. В остальное время смотрит только последний час. Это запускается через cron или планировщик — сам скрипт не крутится в фоне постоянно, а стартует по расписанию, делает свою работу и завершается.&lt;/p&gt;
  &lt;h2 id=&quot;4e24&quot;&gt;Фильтрация в два прохода&lt;/h2&gt;
  &lt;p id=&quot;gK4o&quot;&gt;Самое интересное — как отсеивается мусор. Каналов с вакансиями для дизайнеров много, но процентов восемьдесят контента там не имеет ко мне отношения: UI/UX, инфографика, обложки для ютуба, таргетологи, резюме других людей, которые ищут работу в том же чате. Если пропускать это через нейросеть каждый раз, будет и медленно, и накладно.&lt;/p&gt;
  &lt;p id=&quot;2PBj&quot;&gt;Поэтому первым делом идёт грубый фильтр по ключевым словам. Список стоп-слов (&lt;code&gt;GARBAGE_KEYWORDS&lt;/code&gt;) отсекает явно нерелевантные посты сразу, без всякого ИИ — просто проверка вхождения подстроки. Отдельно отсекаются резюме — по фразам вроде «меня зовут» или «ищу проекты», потому что это не вакансия, а чьё-то самопродвижение. И только если пост прошёл эти фильтры и в нём встретилось хоть одно слово из списка нужных тем (3D, motion, Cinema 4D, Houdini, Redshift и так далее), он попадает на следующий этап.&lt;/p&gt;
  &lt;p id=&quot;FXSF&quot;&gt;Второй этап — локальная модель. Скрипт стучится в Ollama, который крутится на том же компьютере на порту 11434, и просит модель &lt;code&gt;gpt-oss:20b&lt;/code&gt; вынести финальный вердикт: подходит вакансия под профиль 3D/motion-дизайнера или нет. Тут смысл в том, что ключевые слова — штука грубая, они ловят и ложные срабатывания («ищем UI/UX дизайнера с опытом в 3D-иконках» тоже пройдёт по словам). Модель уже смотрит на контекст и отсеивает то, что формально совпало, но по сути не подходит.&lt;/p&gt;
  &lt;p id=&quot;O1O7&quot;&gt;Важный момент — почему это локальная модель, а не запрос к условному GPT через API. Во-первых, дешевле: постов проходит через фильтр по ключевым словам не так много, но за месяц набегает прилично, и гонять их все через платный API смысла нет. Во-вторых, скорость и приватность — не нужно ждать ответа от внешнего сервера и не нужно отправлять куда-то содержимое чужих каналов.&lt;/p&gt;
  &lt;h2 id=&quot;Hc6f&quot;&gt;Что происходит с найденным&lt;/h2&gt;
  &lt;p id=&quot;o9Y5&quot;&gt;Если модель говорит «да», скрипт формирует короткую карточку — заголовок вакансии, название канала, ссылка на пост (или пометка «Private Link», если у канала нет юзернейма) и обрезанное описание — и отправляет всё это самому себе в Telegram, в «Избранное». Получается что-то вроде личной ленты уже отфильтрованных вакансий, куда можно просто иногда заглядывать вместо того, чтобы шерстить два десятка каналов.&lt;/p&gt;
  &lt;p id=&quot;ROqM&quot;&gt;Чтобы не присылать одно и то же дважды, есть простейшая база данных — обычный текстовый файл &lt;code&gt;sent_tasks.txt&lt;/code&gt;, куда пишется уникальный идентификатор сообщения (айди чата плюс айди поста). Перед обработкой скрипт проверяет, нет ли такого идентификатора уже в файле. Никакой базы данных в полном смысле слова тут не нужно — задача маленькая, и текстовый файл справляется без проблем.&lt;/p&gt;
  &lt;h2 id=&quot;Teuq&quot;&gt;Почему так, а не иначе&lt;/h2&gt;
  &lt;p id=&quot;tPSb&quot;&gt;Можно было бы сделать то же самое через официальный Bot API, но тогда пришлось бы добавлять бота в каждый канал отдельно, а часть каналов админы просто не пустят. Через юзер-сессию эта проблема снимается — если я сам состою в канале, скрипт может читать историю переписки.&lt;/p&gt;
  &lt;p id=&quot;pMiB&quot;&gt;Получилось не идеально — иногда модель ошибается, иногда список ключевых слов приходится донастраивать, когда появляется новый тип мусора в лентах. Но по сравнению с ручным листанием двадцати каналов каждый день это уже совсем другой уровень комфорта: просыпаешься, а в «Избранном» уже лежит десяток отфильтрованных вариантов, и можно сразу решать, куда откликаться.&lt;/p&gt;

</content></entry><entry><id>rhv2006:hKFw6GKi-yF</id><link rel="alternate" type="text/html" href="https://teletype.in/@rhv2006/hKFw6GKi-yF?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=rhv2006"></link><title>«Больше никаких 1000 кликов»: Как я заставил C4D рендерить сотни конфигураций без человека</title><published>2026-08-14T12:41:05.974Z</published><updated>2026-08-14T13:16:37.531Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/d2/e4/d2e47002-0ce4-4b69-a49d-68cb798ab8c1.png"></media:thumbnail><summary type="html">Скрипт на Python для Cinema 4D, заменяющий ручной перебор десятков комбинаций «профиль × наполнитель × поддон × угол × материал» единым GUI-инструментом с автоматическим сопоставлением файлов и пакетным рендером.</summary><content type="html">
  &lt;figure id=&quot;H2Wz&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/03/19/03196195-cf07-49dc-be02-9aee05b59f1d.png&quot; width=&quot;2541&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;bCRg&quot;&gt;&lt;em&gt;Скрипт на Python для Cinema 4D, заменяющий ручной перебор десятков комбинаций «профиль × наполнитель × поддон × угол × материал» единым GUI-инструментом с автоматическим сопоставлением файлов и пакетным рендером.&lt;/em&gt;&lt;/p&gt;
  &lt;h2 id=&quot;Tiqx&quot;&gt;&lt;strong&gt;Контекст и задача&lt;/strong&gt;&lt;/h2&gt;
  &lt;p id=&quot;9lZt&quot;&gt;Проект выполнен для компании «Техностиль», занимающейся изготовлением стеновых и кровельных сэндвич-панелей (профилированный металлический лист + утеплитель). Отдел визуализации использует Cinema 4D для генерации PNG-иконок каждого профиля панели, наполнителя (утеплителя — минвата/пенополистирол/PIR-PUR) и поддона — эти изображения затем используются в веб-конфигураторе продукции на сайте компании, где клиент собирает панель нужной комплектации «по кубикам».&lt;/p&gt;
  &lt;p id=&quot;S31g&quot;&gt;До автоматизации 3D-художнику приходилось вручную для каждой единицы номенклатуры: подгружать нужный .ai-файл контура профиля панели, переключать видимость слоёв в сцене, выставлять углы стыковки (90°/135°), задавать материал утеплителя (вата/пенополистирол/PIR-PUR), запускать рендер и раскладывать PNG по правильным папкам. При десятках профилей и наполнителей это часы монотонной ручной работы и источник ошибок в путях сохранения.&lt;/p&gt;
  &lt;p id=&quot;7q22&quot;&gt;Задача скрипта — свести всю эту работу к нескольким кликам: пользователь один раз выбирает угол, материал и режим, отмечает нужные профили/наполнители в чек-листе, и скрипт сам рендерит и раскладывает результат по дисковой структуре.&lt;/p&gt;
  &lt;h2 id=&quot;ukGC&quot;&gt;&lt;strong&gt;Технологический стек&lt;/strong&gt;&lt;/h2&gt;
  &lt;p id=&quot;FKRa&quot;&gt;—  Python (Cinema 4D Python API — модуль c4d)&lt;/p&gt;
  &lt;p id=&quot;f97k&quot;&gt;—  c4d.gui.GeDialog — кастомные модальные GUI-диалоги (чекбоксы, группы, кнопки)&lt;/p&gt;
  &lt;p id=&quot;jScU&quot;&gt;—  c4d.documents.RenderDocument + MultipassBitmap — программный рендер и сохранение PNG с альфа-каналом&lt;/p&gt;
  &lt;p id=&quot;El4p&quot;&gt;—  User Data сцены (ID_USERDATA) — как «пульт управления» видимостью объектов и назначением текстур из скрипта&lt;/p&gt;
  &lt;p id=&quot;KqY4&quot;&gt;—  os / файловая система — сканирование, парсинг имён файлов, построение путей сохранения&lt;/p&gt;
  &lt;h2 id=&quot;I2c6&quot;&gt;&lt;strong&gt;Архитектура решения&lt;/strong&gt;&lt;/h2&gt;
  &lt;p id=&quot;4ymo&quot;&gt;Скрипт построен вокруг одного управляющего объекта сцены RENDER_CONTROL, чьи User Data-параметры выступают интерфейсом между Python-кодом и содержимым 3D-сцены: путь к .ai-файлу контура, путь к файлу наполнителя, путь к поддону, набор булевых «выключателей видимости» (профиль/вата/поддон, замки LZ/PR/PZ, matte-параметры торцов lw/pw) и переключатели угла. Это классический паттерн «оживления» статичной сцены данными снаружи — сцена не меняется, меняются только входные параметры перед каждым рендером.&lt;/p&gt;
  &lt;p id=&quot;gqcm&quot;&gt;Логика выполнения делится на пять последовательных этапов:&lt;/p&gt;
  &lt;p id=&quot;Msvg&quot;&gt;—  Выбор угла раскроя (90° / 135° / без угла) — через нативный c4d.gui.ShowPopupDialog.&lt;/p&gt;
  &lt;p id=&quot;FWRQ&quot;&gt;—  Выбор материала наполнителя (VATA / POLY / PIR) — с автоматическим подбором материала сцены по имени и donастройкой UV-тайлинга текстуры под конкретный материал.&lt;/p&gt;
  &lt;p id=&quot;bJu5&quot;&gt;—  Выбор логического режима рендера (всё вместе, весь профиль, LZ/PR/PZ по отдельности, только наполнитель, только поддон).&lt;/p&gt;
  &lt;p id=&quot;X4Og&quot;&gt;—  Сканирование каталога источника, разбор имён .ai-файлов и группировка через собственные GUI-диалоги (ProfileSelectDialog, NapolnitelSelectDialog).&lt;/p&gt;
  &lt;p id=&quot;5CUU&quot;&gt;—  Цикл рендера: для каждой выбранной позиции скрипт выставляет нужную комбинацию «переключателей» через set_all_switches(), инициирует пересчёт сцены и рендерит кадр в вычисленную по правилам папку.&lt;/p&gt;
  &lt;h2 id=&quot;XUC1&quot;&gt;Ключевые инженерные решения&lt;/h2&gt;
  &lt;h3 id=&quot;tfNy&quot;&gt;&lt;strong&gt;Самодокументируемый диспетчер состояний сцены&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;e8Oa&quot;&gt;Функция set_all_switches() — сердце скрипта. Вместо разбросанных по коду присвоений она собирает все восемь переключателей сцены (видимость профиля/ваты/поддона, три типа замков, два matte-флага торцов) в одну именованную сигнатуру с понятными умолчаниями и докстрингом, объясняющим смысл каждого флага. Любой следующий разработчик может понять логику состояния сцены за 10 секунд, не читая C4D-документацию.&lt;/p&gt;
  &lt;h3 id=&quot;3r3g&quot;&gt;&lt;strong&gt;Эвристический парсер и «спаривание» файлов&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;jlQu&quot;&gt;Файлы поставляются с говорящими, но не строго стандартизированными именами (profil_lz.ai, napolnitel_lz pirpur.ai и т.п.). Функция find_paired_file() очищает префикс и расширение, нормализует регистр и пробелы/подчёркивания и ищет по «чистому» имени соответствующий файл наполнителя или поддона для текущего профиля — с поддержкой суффикса материала (например, pirpur), который отличает, скажем, наполнитель под PIR-PUR от такого же наполнителя под вату.&lt;/p&gt;
  &lt;h3 id=&quot;6O3r&quot;&gt;&lt;strong&gt;Собственные GUI-диалоги с группировкой по смыслу&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;XxKJ&quot;&gt;Вместо стандартного системного диалога «выбери файлы» реализованы два кастомных c4d.gui.GeDialog:&lt;/p&gt;
  &lt;p id=&quot;pLji&quot;&gt;—  ProfileSelectDialog — список профилей, автоматически разделённый на «обычные» и «замковые» (по суффиксу _z), с кнопками «Выбрать все» / «Снять все».&lt;/p&gt;
  &lt;p id=&quot;tUby&quot;&gt;—  NapolnitelSelectDialog — трёхколоночный диалог наполнителей (без префикса / группа Z / группа VENT) с быстрыми фильтрами «только Z», «только VENT» и т. д., чтобы не листать десятки чекбоксов вручную.&lt;/p&gt;
  &lt;p id=&quot;AbOL&quot;&gt;Оба диалога используют единый приём: диапазон ID чекбоксов (CHECK_BASE) плюс индекс в объединённом списке, что позволяет одной строкой восстановить, какие позиции пользователь отметил, без хранения отдельного состояния на каждый чекбокс.&lt;/p&gt;
  &lt;h3 id=&quot;6k9Z&quot;&gt;&lt;strong&gt;Предсказуемая структура сохранения результатов&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;HcWo&quot;&gt;Путь сохранения PNG собирается динамически из нескольких независимых переменных: базовая папка → флаг «замки» → тип объекта (profil/napolnitel/poddon) → материал наполнителя → подпапка угла (90°/135°). Такой подход гарантирует, что структура папок веб-конфигуратора всегда предсказуема и не зависит от порядка выбора пользователем — а для повторяющегося поддона (одинакового для группы профилей) отдельный флаг poddon_rendered_once исключает лишние дубли рендера.&lt;/p&gt;
  &lt;h3 id=&quot;u7t0&quot;&gt;&lt;strong&gt;Защита от «пустого» рендера&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;CIqX&quot;&gt;Перед каждым фактическим рендером сцена дважды прогоняется через wait_for_scene() (ExecutePasses + GeSyncMessage), чтобы Cinema 4D успела пересчитать зависимые генераторы и текстуры после смены User Data — иначе первый кадр в серии рискует захватить ещё не обновлённое состояние сцены. Это распространённая, но неочевидная проблема программного рендера в C4D, решённая явным ожиданием синхронизации.&lt;/p&gt;
  &lt;h2 id=&quot;9RDC&quot;&gt;&lt;strong&gt;Пример потока выполнения&lt;/strong&gt;&lt;/h2&gt;
  &lt;p id=&quot;IDCt&quot;&gt;—  находит парный файл наполнителя под PIR-PUR и рендерит его отдельным проходом с другим набором переключателей видимости;&lt;/p&gt;
  &lt;p id=&quot;PgTx&quot;&gt;—  рендерит поддон один раз на группу профилей без замков;&lt;/p&gt;
  &lt;p id=&quot;Z74w&quot;&gt;—  сохраняет все PNG по маршруту вида .../zamki/profil/uglovie/135°/... и по завершении открывает соответствующие папки в проводнике.&lt;/p&gt;
  &lt;h2 id=&quot;FgF7&quot;&gt;&lt;strong&gt;Итог&lt;/strong&gt;&lt;/h2&gt;
  &lt;p id=&quot;hjS5&quot;&gt;Скрипт превращает многочасовую рутинную задачу подготовки графики для конфигуратора в управляемый процесс из нескольких кликов: 3D-художник задаёт только высокоуровневые параметры (угол, материал, что рендерить), а сопоставление файлов, переключение состояний сцены, построение путей и пакетный рендер выполняются автоматически и воспроизводимо.&lt;/p&gt;
  &lt;p id=&quot;QC9R&quot;&gt;С инженерной точки зрения работа демонстрирует практическое владение Python-интеграцией со сторонним DCC-приложением (Cinema 4D SDK), проектирование пользовательских GUI поверх непривычного тулкита, устойчивый к вариативности входных данных парсинг имён файлов и аккуратную декомпозицию сложной ветвящейся логики (шесть режимов рендера) на переиспользуемые функции.&lt;/p&gt;

</content></entry><entry><id>rhv2006:5_4evRjEqCe</id><link rel="alternate" type="text/html" href="https://teletype.in/@rhv2006/5_4evRjEqCe?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=rhv2006"></link><title>Минус 80% рутины на ресайзах: Как я заавтоматизировал верстку для T2 с помощью ExtendScript</title><published>2026-08-14T10:13:27.042Z</published><updated>2026-08-14T13:17:44.610Z</updated><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/21/72/21720210-d84b-43bf-a4c3-645cd736b6a1.png&quot;&gt;Комплекс скриптов написан на JavaScript / ExtendScript для автоматизации повседневных рутинных операций в Adobe Illustrator. Набор инструментов полностью убирает человеческий фактор при предпечатной подготовке (prepress), построении модульных сеток, выравнивании элементов и адаптивном экспорте.</summary><content type="html">
  &lt;figure id=&quot;aWeT&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/21/72/21720210-d84b-43bf-a4c3-645cd736b6a1.png&quot; width=&quot;475&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;lT5Q&quot;&gt;Контекст и решаемая задача&lt;/h2&gt;
  &lt;p id=&quot;LaUM&quot;&gt;Комплекс скриптов написан на &lt;strong&gt;JavaScript / ExtendScript&lt;/strong&gt; для автоматизации повседневных рутинных операций в Adobe Illustrator. Набор инструментов полностью убирает человеческий фактор при предпечатной подготовке (prepress), построении модульных сеток, выравнивании элементов и адаптивном экспорте.&lt;/p&gt;
  &lt;p id=&quot;VX14&quot;&gt;&lt;strong&gt;Ключевые показатели эффективности:&lt;/strong&gt;&lt;/p&gt;
  &lt;ul id=&quot;oqsD&quot;&gt;
    &lt;li id=&quot;86HU&quot;&gt;&lt;strong&gt;В 10–15 раз&lt;/strong&gt; ускорение пакетного экспорта и верстки ресайзов.&lt;/li&gt;
    &lt;li id=&quot;7EEz&quot;&gt;&lt;strong&gt;0% ошибок&lt;/strong&gt; при предпечатной подготовке (исключены пропущенные шрифты, незалинкованные файлы и некорректная цветовая модель).&lt;/li&gt;
    &lt;li id=&quot;jj1H&quot;&gt;&lt;strong&gt;100% соблюдение гайдлайнов&lt;/strong&gt; за счёт математического расчёта полей и отступов&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;IE6l&quot;&gt;Разбор ключевых модулей&lt;/h2&gt;
  &lt;p id=&quot;S7Dk&quot;&gt;&lt;strong&gt;Модульная сетка и гайды (&lt;code&gt;runT2Grid&lt;/code&gt;, &lt;code&gt;scriptOne&lt;/code&gt;, &lt;code&gt;scriptTwo&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;
  &lt;ul id=&quot;FlCJ&quot;&gt;
    &lt;li id=&quot;g5Ju&quot;&gt;&lt;strong&gt;Что делает:&lt;/strong&gt; Автоматически строит внешнюю рамку безопасности и 6 опорных направляющих (центральные оси + пропорциональные сдвиги).&lt;/li&gt;
    &lt;li id=&quot;ds9t&quot;&gt;&lt;strong&gt;Логика:&lt;/strong&gt; Все отступы рассчитываются динамически от меньшей стороны артборда: &lt;code&gt;var minSide = Math.min(width, height)&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;RPga&quot;&gt;&lt;strong&gt;Зачем:&lt;/strong&gt; Мгновенная разметка артборда любого формата под требования брендбука.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;R3dZ&quot;&gt;&lt;strong&gt;Умное распределение элементов (&lt;code&gt;runPaddings&lt;/code&gt;, &lt;code&gt;scriptSeven&lt;/code&gt;, &lt;code&gt;scriptEight&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;
  &lt;ul id=&quot;lFN0&quot;&gt;
    &lt;li id=&quot;iUHL&quot;&gt;&lt;strong&gt;Что делает:&lt;/strong&gt; Выравнивает выбранные объекты вдоль вертикальной или горизонтальной оси с фиксированным шагом.&lt;/li&gt;
    &lt;li id=&quot;H7r6&quot;&gt;&lt;strong&gt;Логика:&lt;/strong&gt; Определяет координаты, сортирует объекты по порядку (&lt;code&gt;top&lt;/code&gt; или &lt;code&gt;left&lt;/code&gt;) и выстраивает их с идеальным равным интервалом, не позволяя вылезти за края артборда.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;dy4S&quot;&gt;&lt;strong&gt;Пропорциональное скругление углов (&lt;code&gt;runRoundCorners&lt;/code&gt;, &lt;code&gt;scriptThree&lt;/code&gt;, &lt;code&gt;scriptFour&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;
  &lt;ul id=&quot;w0HD&quot;&gt;
    &lt;li id=&quot;QVrW&quot;&gt;&lt;strong&gt;Что делает:&lt;/strong&gt; Находит среди выделения замкнутые прямоугольники и применяет к ним адаптивный радиус скругления.&lt;/li&gt;
    &lt;li id=&quot;2IX3&quot;&gt;&lt;strong&gt;Логика:&lt;/strong&gt; Проверяет топологию объекта (4 точки, равенство противоположных сторон) и пересоздаёт его как &lt;code&gt;roundedRectangle&lt;/code&gt; с сохранением исходных заливок и обводок.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;BnpF&quot;&gt;&lt;strong&gt;Пакетный Prepress и экспорт (&lt;code&gt;scriptFive&lt;/code&gt;, &lt;code&gt;scriptSix&lt;/code&gt;, &lt;code&gt;scriptTwelve&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;
  &lt;ul id=&quot;KS9w&quot;&gt;
    &lt;li id=&quot;a6nc&quot;&gt;&lt;strong&gt;Что делает:&lt;/strong&gt; Полный цикл подготовки к печати:&lt;/li&gt;
    &lt;ol id=&quot;JX5u&quot;&gt;
      &lt;li id=&quot;n3cP&quot;&gt;Автоматический перевод всего текста в кривые.&lt;/li&gt;
      &lt;li id=&quot;1Rc2&quot;&gt;Сбор всех связанных изображений (placed items) в единую папку &lt;code&gt;/links&lt;/code&gt;.&lt;/li&gt;
      &lt;li id=&quot;K8cc&quot;&gt;Экспорт векторов в PDF с вылетами под обрез и метками реза.&lt;/li&gt;
      &lt;li id=&quot;1gEZ&quot;&gt;Генерация сборных JPEG-превью для всех артбордов.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;2Pjl&quot;&gt;&lt;strong&gt;Пакетный поиск и замена «Ищейка» (&lt;code&gt;scriptThirteen_Ischeika&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;
  &lt;ul id=&quot;3ngx&quot;&gt;
    &lt;li id=&quot;FQii&quot;&gt;&lt;strong&gt;Что делает:&lt;/strong&gt; Рекурсивно сканирует вложенные папки, находит текстовые блоки в AI-файлах, делает замену, пересчитывает размеры рамок и сохраняет результаты.&lt;/li&gt;
    &lt;li id=&quot;dLOk&quot;&gt;&lt;strong&gt;Уникальная фича (&lt;code&gt;fitTextInFrame&lt;/code&gt;):&lt;/strong&gt; Если новый текст не помещается, скрипт автоматически конвертирует &lt;code&gt;PointText&lt;/code&gt; в &lt;code&gt;AreaText&lt;/code&gt; и динамически увеличивает рамку вниз до устранения переполнения (&lt;code&gt;overflows === false&lt;/code&gt;).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;JoIS&quot;&gt;&lt;strong&gt;Генерация ресайзов из данных (&lt;code&gt;scriptTen&lt;/code&gt;, &lt;code&gt;scriptEleven_MassVizitki&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;
  &lt;ul id=&quot;sGab&quot;&gt;
    &lt;li id=&quot;FbZl&quot;&gt;&lt;strong&gt;Что делает:&lt;/strong&gt; Считывает CSV-файлы с размерами или техническими данными и автоматически генерирует файлы нужных габаритов с точной привязкой элементов.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;QnlJ&quot;&gt;Технические особенности реализации&lt;/h2&gt;
  &lt;p id=&quot;p-rc_bdf5d1ea8ab7ef62-75&quot;&gt;&lt;strong&gt;Безопасная работа с файловой системой (I/O):&lt;/strong&gt; Реализованы кастомные функции контроля записи &lt;code&gt;waitForFileWritten&lt;/code&gt; и освобождения памяти &lt;code&gt;waitForDocClosed&lt;/code&gt;, что исключает появление битых PDF/JPG или зависание Illustrator при фоновой обработке сотен файлов.&lt;/p&gt;
  &lt;p id=&quot;p-rc_bdf5d1ea8ab7ef62-76&quot;&gt;&lt;strong&gt;Адаптивная математика (Relative Scaling):&lt;/strong&gt; Отсутствие жесткого хардкода размеры/отступы считаются в процентах/долях от меньшей стороны текущего артборда (&lt;code&gt;Math.min(W, H)&lt;/code&gt;), что делает скрипты универсальными для макетов любого масштаба.&lt;/p&gt;
  &lt;p id=&quot;p-rc_bdf5d1ea8ab7ef62-77&quot;&gt;&lt;strong&gt;UI/UX интерфейс:&lt;/strong&gt; Все сложные скрипты снабжены удобными диалоговыми окнами (&lt;code&gt;ScriptUI&lt;/code&gt;) с выбором параметров экспорта (DPI 72/150/300, качество JPEG, метки реза, выбор папок).&lt;/p&gt;
  &lt;p id=&quot;VYEJ&quot;&gt;Эти скрипты — пример того, как я оптимизирую рутину в дизайне и верстке. Вместо того чтобы вручную переводить 50 макетов в кривые, выставлять метки реза и раскладывать файлы по папкам, я написал скрипты на JS/ExtendScript. Они выполняют часовую работу за пару минут, гарантируют отсутствие технических ошибок и позволяют команде фокусироваться на креативных задачах, а не на технической рутине&lt;/p&gt;

</content></entry></feed>