Приближается лето. Могут потребоваться обновления в системе полива, например, разветвители для шлангов. У меня есть несколько 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)


  1. Byaka8kaka
    30.05.2025 13:57

    Спс, улыбнуло) Ждем когда ИИ всех нас заменит и начнет прокладывать такие трубы


  1. unC0Rr
    30.05.2025 13:57

    Почему вам в голову пришла использовать чат-бота для генерации трёхмерных моделей вместо специализированных на этой задаче моделей?


    1. vened Автор
      30.05.2025 13:57

      Есть целый ряд причин.

      Во-первых, эта штука не написала в ответ, что, мол, "вижу, тут требуется 3D-объект, но я всего лишь чат-бот - обратитесь к специализированной системе". Напротив - оно прямо и уверенно пишет, что точно умеет в OpenSCAD, знает все детали, приводит примеры (я спрашивал), а также предлагает проверить, что всё в коде корректно и верно при помощи рендеринга в OpenSCAD (да, такой вот "напор" демонстрирует). Опять же - что такое "чат-бот"? Если это средство занять назойливых клиентов в чате технической поддержки пустопорожним переливанием запроса в ответ - ну, да, тогда с OpenSCAD лучше не подходить. Но позиционируется-то этот инструмент явно иначе.

      Во-вторых, не то чтобы была у меня какая-то уверенность, но я предположил, что в эту систему, - возможно! - уже успели-таки встроить "специализированное решение для 3D". Это разумное предположение: такая возможность, без сомнения, полезна, системы постоянно обновляют, ждут в этом году "универсальный ИИ", то и дело утверждается, что оно успешно решает задачи по геометрии на уровне "продвинутого старшеклассника". Вот и проверили. Если бы оно справилось, это бы не сделало его интеллектом, но результат бы порадовал.

      В-третьих, программное, процедурное задание простейших 3D-объектов - вообще-то не сложнее, скажем, реализации алгоритмов быстрой сортировки больших массивов, алгоритмов обработки графов, алгоритмов балансировки параллельной работы с данными и т.д., и т.п. В компьютерной геометрии есть свои сложности, но они точно не в генерировании описания трёх цлиндрических трубочек и параллелепипеда (предположим). Вся такая простая 3D-геометрия - выписывается в векторах, если хотите. С этой геометрией в видеоиграх справляются те же видеокарты, на которых запускают эти же LLM. Задача процедурного определения простейшего 3D-объекта, между прочим, не сложнее и поиска "уязвимостей в ПО". И уж тем более теряется сложность на фоне разговоров о генерировании видео по текстовому описанию. Конечно, всё это верно только в том случае, если там настоящий интеллект, который именно решает задачу, а не синонимайзер, замещающий решение сгенерированным текстом (или иерархией кластеров пикселей - не важно). Собственно, поэтому-то тут и возможны специализированные решения.

      В-четвёртых, постоянно вижу и слышу, как LLM типа "чат-бот" используют для генерации программного кода - для этого ведь даже есть плагины в средах разработки. Код OpenSCAD - это чисто программный код, я же не просил рисовать чертёж в изометрии. Всё подходит.

      Наконец, в-пятых: в качестве специализированной системы - я бы всё же предпочёл использовать тот или иной готовый инструмент для параметрического задания типовых объектов в OpenSCAD (Y-образный адаптер к ним тоже относится). Такие инструменты есть. Они детерминированы. Они проще. Натыкать в интерфейсе размеры - быстро. Но они - не LLM, на которых обещают "универсальный интеллект". Собственно, есть ведь и немало инструментов "визуального программирования". Даже для специальных систем, типа GNU Radio, но почему-то сейчас про них вообще забыли, а рассказывают именно про генерирование кода LLM, на которые всех заменят.


  1. 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();


    1. 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();