Приближается лето. Могут потребоваться обновления в системе полива, например, разветвители для шлангов. У меня есть несколько 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();
Комментарии (5)
unC0Rr
30.05.2025 13:57Почему вам в голову пришла использовать чат-бота для генерации трёхмерных моделей вместо специализированных на этой задаче моделей?
vened Автор
30.05.2025 13:57Есть целый ряд причин.
Во-первых, эта штука не написала в ответ, что, мол, "вижу, тут требуется 3D-объект, но я всего лишь чат-бот - обратитесь к специализированной системе". Напротив - оно прямо и уверенно пишет, что точно умеет в OpenSCAD, знает все детали, приводит примеры (я спрашивал), а также предлагает проверить, что всё в коде корректно и верно при помощи рендеринга в OpenSCAD (да, такой вот "напор" демонстрирует). Опять же - что такое "чат-бот"? Если это средство занять назойливых клиентов в чате технической поддержки пустопорожним переливанием запроса в ответ - ну, да, тогда с OpenSCAD лучше не подходить. Но позиционируется-то этот инструмент явно иначе.
Во-вторых, не то чтобы была у меня какая-то уверенность, но я предположил, что в эту систему, - возможно! - уже успели-таки встроить "специализированное решение для 3D". Это разумное предположение: такая возможность, без сомнения, полезна, системы постоянно обновляют, ждут в этом году "универсальный ИИ", то и дело утверждается, что оно успешно решает задачи по геометрии на уровне "продвинутого старшеклассника". Вот и проверили. Если бы оно справилось, это бы не сделало его интеллектом, но результат бы порадовал.
В-третьих, программное, процедурное задание простейших 3D-объектов - вообще-то не сложнее, скажем, реализации алгоритмов быстрой сортировки больших массивов, алгоритмов обработки графов, алгоритмов балансировки параллельной работы с данными и т.д., и т.п. В компьютерной геометрии есть свои сложности, но они точно не в генерировании описания трёх цлиндрических трубочек и параллелепипеда (предположим). Вся такая простая 3D-геометрия - выписывается в векторах, если хотите. С этой геометрией в видеоиграх справляются те же видеокарты, на которых запускают эти же LLM. Задача процедурного определения простейшего 3D-объекта, между прочим, не сложнее и поиска "уязвимостей в ПО". И уж тем более теряется сложность на фоне разговоров о генерировании видео по текстовому описанию. Конечно, всё это верно только в том случае, если там настоящий интеллект, который именно решает задачу, а не синонимайзер, замещающий решение сгенерированным текстом (или иерархией кластеров пикселей - не важно). Собственно, поэтому-то тут и возможны специализированные решения.
В-четвёртых, постоянно вижу и слышу, как LLM типа "чат-бот" используют для генерации программного кода - для этого ведь даже есть плагины в средах разработки. Код OpenSCAD - это чисто программный код, я же не просил рисовать чертёж в изометрии. Всё подходит.
Наконец, в-пятых: в качестве специализированной системы - я бы всё же предпочёл использовать тот или иной готовый инструмент для параметрического задания типовых объектов в OpenSCAD (Y-образный адаптер к ним тоже относится). Такие инструменты есть. Они детерминированы. Они проще. Натыкать в интерфейсе размеры - быстро. Но они - не LLM, на которых обещают "универсальный интеллект". Собственно, есть ведь и немало инструментов "визуального программирования". Даже для специальных систем, типа GNU Radio, но почему-то сейчас про них вообще забыли, а рассказывают именно про генерирование кода LLM, на которые всех заменят.
peacemakerv
30.05.2025 13:57Ну, все-таки, результат сильно зависит от зравомыслия промпта. Вот без усложнений, вполне реализуется:
Скрытый текст
Напиши код OpenSCAD для разветвителя гибких шлангов, для воды. Это разветвитель один-в-два, то есть, одна входная трубка и две выходных, т.е. форма изделия Y-образная. Диаметр 35 мм, толщина стенки 2мм. Должно быть цельное изделие, напоминающее рогатку. Внутренний объем всех трех трубок должен быть свободен для протекания воды.
$fn = 100; // Качество рендеринга // Основные параметры diameter = 35; // Внешний диаметр трубок wall = 2; // Толщина стенок length = 100; // Длина каждой ветви angle = 45; // Угол разветвления (градусы) ridge_height = 3; // Высота ограничительного бортика ridge_width = 2; // Выступ бортика // Рассчитанные параметры inner_dia = diameter - 2*wall; // Внутренний диаметр transition_dia = diameter*1.5; // Диаметр центрального узла // Параметры отображения alpha = 0.7; // Прозрачность (0-1, где 1 - полностью непрозрачный) module hollow_tube(l, d) { difference() { cylinder(h=l, d=d); translate([0, 0, -0.1]) cylinder(h=l+0.2, d=inner_dia); } } module end_ridge() { difference() { cylinder(h=ridge_height, d=diameter + 2*ridge_width); translate([0, 0, -0.1]) cylinder(h=ridge_height+0.2, d=diameter); } } module y_connector() { union() { // Центральный переходный узел hull() { sphere(d=transition_dia); // Основание входной трубки translate([0, 0, -diameter/2]) sphere(d=diameter); // Основания выходных трубок for(a = [angle, -angle]) { rotate([0, a, 0]) translate([0, 0, diameter/2]) sphere(d=diameter); } } // Входная трубка translate([0, 0, -length]) union() { hollow_tube(length, diameter); translate([0, 0, length-ridge_height]) end_ridge(); } // Выходные трубки for(a = [angle, -angle]) { rotate([0, a, 0]) translate([0, 0, diameter/2]) union() { hollow_tube(length, diameter); translate([0, 0, length-ridge_height]) end_ridge(); } } } } // Основная модель с прозрачностью module y_splitter() { color("LightBlue", alpha) { // Голубой цвет с прозрачностью difference() { y_connector(); // Внутренние полости (для гарантии протока) // Центральная полость sphere(d=inner_dia); // Входная полость translate([0, 0, -length-0.1]) cylinder(h=length+0.1, d=inner_dia); // Выходные полости for(a = [angle, -angle]) { rotate([0, a, 0]) translate([0, 0, -0.1]) cylinder(h=length+0.1, d=inner_dia); } } } // Визуализация внутренних полостей (красный цвет) %color("red", 0.3) { // Центральная полость sphere(d=inner_dia); // Входная полость translate([0, 0, -length-0.1]) cylinder(h=length+0.1, d=inner_dia); // Выходные полости for(a = [angle, -angle]) { rotate([0, a, 0]) translate([0, 0, -0.1]) cylinder(h=length+0.1, d=inner_dia); } } } y_splitter();
vened Автор
30.05.2025 13:57Ну, все-таки, результат сильно зависит от зравомыслия промпта.
Это да. Но более простые - дают результат хуже.
Вот без усложнений, вполне реализуется:
Вообще, тут утрачены параллельные выводные трубки, но откуда-то взяты буртики (их нет в промпте). Но дело даже в не в этом - вот результат, который мне выдёт GPT-4o по этому же промпту:
Трубки Скрытый текст
Напиши код OpenSCAD для разветвителя гибких шлангов, для воды. Это разветвитель один-в-два, то есть, одна входная трубка и две выходных, т.е. форма изделия Y-образная. Диаметр 35 мм, толщина стенки 2мм. Должно быть цельное изделие, напоминающее рогатку. Внутренний объем всех трех трубок должен быть свободен для протекания воды.
$fn = 100; // сглаживание округлостей // Параметры outer_diameter = 35; wall_thickness = 2; inner_diameter = outer_diameter - 2 * wall_thickness; length = 50; // длина каждого ответвления // Создание одной трубки с внутренним отверстием module pipe_segment(len) { difference() { cylinder(h = len, d = outer_diameter, center = false); translate([0, 0, -1]) cylinder(h = len + 2, d = inner_diameter, center = false); } } // Поворот и размещение боковых трубок module y_splitter() { union() { // Центральная (входная) трубка translate([0, 0, 0]) pipe_segment(length); // Левая выходная трубка, под углом 45° translate([0, 0, length]) rotate([45, 0, -45]) pipe_segment(length); // Правая выходная трубка, под углом 45° translate([0, 0, length]) rotate([45, 0, 45]) pipe_segment(length); } } y_splitter();
Byaka8kaka
Спс, улыбнуло) Ждем когда ИИ всех нас заменит и начнет прокладывать такие трубы