Приближается лето. Могут потребоваться обновления в системе полива, например, разветвители для шлангов. У меня есть несколько 3d-принтеров, по крайней мере на одном из них можно такой разветвитель распечатать. По сравнению с прошлым годом сильно возросла популярность ИИ-инструментов, которые вот-вот должны заменить привычные шаги разработки - например, "не нужно будет уметь программировать". OpenSCAD - полезнейшая система, позволяющая процедурными методами создавать сложные 3D-модели: пишете код, как обычную программу, - а система генерирует модель. Попробуем сгенерировать процедурный код OpenSCAD для модели разветвителя шлангов при помощи ChatGPT.
Прежде всего, составим максимально подробное описание на обычном русском языке. Описание хоть и подробное, но это далеко не конструкторская документация, что полностью соответствует "целям и задачам" "прорывных" ИИ-инструментов, как они сейчас позиционируются: "вайбкодинг" в чистом виде, так сказать, но для OpenSCAD. Конечно, я могу и сам писать код OpenSCAD, а составление столь подробного описания заняло время, сравнимое с временем, нужным для написания искомой модели сразу в OpenSCAD - там ничего сложного нет. Но представьте, что человек с OpenSCAD ранее не сталкивался - вот тут бы очень пригодились возможности продвинутых ИИ, которые "заменят специалистов". К сожалению, на практике замена не задалась, а всё вышло иначе.
Итак, вот промпт целиком:
"Напиши код OpenSCAD для разветвителя гибких шлангов, для воды. Это разветвитель один-в-два, то есть, одна входная трубка и две выходных. Вода должна проходить через разветвитель из входного шланга в выходные. Трубки разветвителя вставляются внутрь шлангов, сверху шланги притягиваются хомутами. Разветвитель является единым изделием, не должно быть посторонних отверстий. Входная трубка находится с противоположной стороны от выходных. Выходные трубки параллельны, но должно быть оставлено место для размещения шлангов и хомутов. Вокруг внешнего края каждой трубки должен быть предусмотрен невысокий и короткий ободок для предотвращения соскальзывания шланга. Внутренний диаметр шланга, который надевается на входную трубку - 35 миллиметров. Внутренние диаметры выходных шлангов - 15 мм. Трубки должны быть чуть большего диаметра (например, на одну двадцатую от диаметра шланга), чтобы шланги хорошо держались за счёт трения и не сморщивались при затягивании хомутов. Ободки по краям трубок должны быть больше диаметром, чем соответствующая трубка, ещё на одну двадцатую диаметра шланга. Длина входной трубки под шланг - как минимум, три диаметра шланга, то есть 105 мм. Длина выходных трубок под шланг, соответственно, 45 мм. Стенки трубок разветвителя не должны быть толще двух миллиметров."
Y-образный разветвитель, наверное, один из самых распространённых элементов, если не самый распространённый. Для описания подобных изделий есть немало готовых скриптов в вебе. Есть готовые параметрические редакторы и так далее. То есть, не сказать, что это что-то редкое. Как раз соответствует ожиданиям - хотелось бы поэкономить на "кодинге" и получить отличное готовое решение. В промпте - всё хорошо. ChatGPT подтверждает. Если бы только "интеллектуальные" LLM могли сразу сказать, что не умеют в OpenSCAD. Но нет. "Стохастический попугай" настроен на результат. На какой? Да на любой.
Вариант первый. Это GPT-4o. Код, выданный LLM, содержит подробные комментарии, в сопроводительном тексте утверждается, например, что "соединения герметичны: нет лишних отверстий, внутренние каналы цельные". То есть, если посмотреть только на выдачу LLM и на код, не понимая сути, то всё выглядит более чем цивильно: код валидный, а обёртка настолько хороша, что несведущий наблюдатель так и сказал бы: "ИИ очередной раз успешно решил задачу". Но посмотрим на скриншот результата, после рендеринга модели по этому коду в OpenSCAD:

Даже на Enterprise из сериала Star Trek не очень-то похоже. И это самый лучший вариант. Он, хоть и весьма отдалённо, но всё же напоминает описанное в задании. Естественно, ни о какой герметичности тут говорить не приходится. Ну, формально, шланги на цилиндры можно надеть. Не более того. Бесполезный результат. Его бесполезность многократно усиливается тем, что ChatGPT преподносит код как верный и качественный.
Я попробовал тот же промпт в o4-mini и GPT-4.1-mini. Вот вариант второй - результат рендеринга кода o4-mini:

GPT-4.1-mini чуть лучше, если только так можно сказать, но тоже какая-то плохая пародия:

Сгенерированные исходные коды оказались бесполезны. Для тех, кто не знаком с OpenSCAD, но хотел бы разобраться, они, к сожалению, не дают ничего ценного, так как слишком запутанны и переусложнены. Код от gpt-4o, для примера, дан полностью в конце статьи (на другие варианты смысла смотреть нет вовсе). Вариант от gpt-4o начинается с блока переменных-параметров, которые должны задавать размеры прочих элементов. Как способ оформления кода - очень хорошо. Вот только эти параметры далее используются не так, как должны бы. Например, есть параметр wall_thickness = 2 - видимо, это прямое превращение фрагмента промпта, в котором сказано, что стенки не должны быть толще двух миллиметров. Вот только параметр этот используется не для задания толщины стенок (то есть, не в качестве значения разности диаметров внешнего и внутреннего цилиндров), а для построения ненужной "оболочки" при помощи hull(): в OpenSCAD функция hull() позволяет задавать выпуклые оболочки, охватывающие заданные объекты - но в коде от ChatGPT в качестве этих задающих объектов описаны какие-то произвольные цилиндры, у которых диаметр как бы равен внешнему диаметру входной и выходных трубок, плюс "толщина стенки", умноженная на два (tube_out_d + wall_thickness*2) - зачем и почему? В комментариях на это ответа нет. Понятно, что просто был взят какой-то код и "синонимизирован", это подтверждается умножением на два - в OpenSCAD цилиндры задаются диаметрами, соответственно, если у вас стенка задана "по радиусу", то нужно умножить параметр на два, чтобы пересчитать в диаметр. Вот только делаться это здесь должно не в hull().
То же и с прочими параметрами. Так, внутренний диаметр входной трубки делается равным внутреннему (видимо, ведь в коде про это ничего не сказано) диаметру входного шланга (hose_in_d = 35), а параметр толщины стенки здесь не используется. И так далее, и тому подобное. Можно было бы обсуждать детали, если бы код модели был примерно похож на рабочее изделие, но здесь этого нет и близко, как можно убедиться по скриншотам.
А вот что получилось в ответ на простой промпт, который мог бы задать "обычный пользователь" ИИ, как его сейчас постоянно рисуют в СМИ:
"Сгенерируй на языке OpenSCAD описание модели адаптера, который позволяет один шланг 35 мм присоединить к двум шлангам 15 мм, чтобы распечатать на 3d-принтере."

Одна из основных опасностей ИИ в том, что только по обёртке и вправду могут признать результат качественным. Такой вот "вайб-шлангинг".
(Код OpenSCAD от GPT-4o, полученный по первому, подробному, промпту.)
Скрытый текст
// Параметры шлангов
hose_in_d = 35; // внутренний диаметр входного шланга
hose_out_d = 15; // внутренний диаметр выходного шланга
in_length = 3 * hose_in_d; // длина входной трубки — 105 мм
out_length = 3 * hose_out_d; // длина выходных трубок — 45 мм
wall_thickness = 2; // толщина стенки трубки
fit_factor = 1 + 1/20; // трубка чуть больше шланга
rim_factor = 1 + 2/20; // ободок ещё больше
// Вычисленные размеры
tube_in_d = hose_in_d * fit_factor; // внешний диаметр входной трубки
tube_out_d = hose_out_d * fit_factor; // внешний диаметр выходных трубок
rim_in_d = hose_in_d * rim_factor; // внешний диаметр ободка входа
rim_out_d = hose_out_d * rim_factor; // внешний диаметр ободка выхода
rim_height = 2; // высота ободков
module inlet_tube() {
difference() {
// наружная трубка
union() {
cylinder(h = in_length, d = tube_in_d, $fn=100);
cylinder(h = rim_height, d = rim_in_d, $fn=100);
}
// внутреннее отверстие
translate([0,0,0])
cylinder(h = in_length, d = hose_in_d, $fn=100);
}
}
module outlet_tube() {
difference() {
// наружная трубка
union() {
cylinder(h = out_length, d = tube_out_d, $fn=100);
translate([0, 0, out_length - rim_height])
cylinder(h = rim_height, d = rim_out_d, $fn=100);
}
// внутреннее отверстие
translate([0,0,0])
cylinder(h = out_length, d = hose_out_d, $fn=100);
}
}
// основное тело и соединение каналов
module splitter() {
// Тело: блок, соединяющий трубки
body_len = 30;
body_w = tube_out_d * 2 + 10; // расстояние между выходами с запасом
body_h = tube_out_d + 10;
// Создаём корпус
difference() {
hull() {
translate([0, -body_w/2, 0])
cylinder(h = body_len, d = tube_out_d + wall_thickness*2, $fn=100);
translate([0, body_w/2, 0])
cylinder(h = body_len, d = tube_out_d + wall_thickness*2, $fn=100);
translate([-body_len/2, 0, 0])
cylinder(h = body_len, d = tube_in_d + wall_thickness*2, $fn=100);
}
// Каналы внутри
channel_d = hose_out_d; // внутренние каналы по размеру шлангов
// От входа к центру
translate([-body_len/2 - 1, 0, 0])
rotate([90, 0, 0])
cylinder(h = body_len + 2, d = hose_in_d, $fn=100);
// К выходам
translate([0, -body_w/2, 0])
rotate([0, 90, 0])
cylinder(h = body_len + 2, d = hose_out_d, $fn=100);
translate([0, body_w/2, 0])
rotate([0, 90, 0])
cylinder(h = body_len + 2, d = hose_out_d, $fn=100);
}
// Добавляем входную трубку
translate([-in_length, 0, 0])
rotate([0, 90, 0])
inlet_tube();
// Выходные трубки
translate([body_len, -body_w/2, 0])
rotate([0, -90, 0])
outlet_tube();
translate([body_len, body_w/2, 0])
rotate([0, -90, 0])
outlet_tube();
}
// Главный вызов
splitter();