Я тут всё пытаюсь OpenSCAD к чему-нибудь полезному приспособить, и как-то результаты пока не радуют. Не то чтобы оно прям совсем не работало, глючило или, там, падало. Нет, работает. Не падает. И люди ж как-то делают свой дизайн в нём (как - мы к этому еще вернемся), но есть пара невероятно всратых "особенностей", которые превращают пользование им вот в это самое. Хуже всего, что это особенности принципиальные, которые не исправить никаким разумным количеством программерской работы и без ломки совместимости.
Момент первый. Геометрическое ядро OpenSCAD'а построено вокруг булевых операций над полигональными мешами, а вовсе не над примитивами, как обещает документация. Причём, этот момент тщательно замыливается. На самом же деле примитивы - просто синтаксический сахар в языке, который на месте генерирует соответствующий меш, а далее всё поступает в libCGAL, где и процессируется. Казалось бы: ну и что, очередной не чистый концептуально язык, какая жалость! Но есть нюанс - в отличие от boundary representation моделера, где тесселяция будет финальным этапом, здесь она будет первым. Который и определит дальнейшее качество всей последующей работы.
Представим себе деталь типа "телевизор" (в смысле, олдфажный телевизор, с трубкой). Это поверхность с большим радиусом кривизны (типично сфера), из которой выкушен маленький кусочек в середине. Как и следовало ожидать, при слишком грубом шаге тесселяции мы выкусим из полигонального приближения сферы один-два полигона, дающие нам вместо плавной округлости там где она должна быть, одну-две поверхности, стыкующиеся под странным углом. При мелком шаге, тоже ожидаемо, получаем плавную поверхность, но геометрический движок вынужден перемалывать десятки и сотни тысяч полигонов, большая часть из которых будет отброшена. Можно и не дождаться (я лично при $fn=1000 не дождался, а $fn=100 слишком грубо). У BREP-моделера здесь есть хотя-бы теоретическая возможность оптимизации.
Можно, в принципе, самому заниматься оптимизацией, писать какие-то свои процедуры, строящие оптимизированный кусок поверхности сразу... такой местный эквивалент обычного сиплюсплюсного байтоёбства. Народ, в принципе, так и делает - выкидывает нахрен сразу встроенные примитивы и алгоритмы, и пишет свой геометрический движок. При этом повторяя всё с нуля в каждой библиотеке, начиная с формулы точек на окружности - [r*cos(a), r*sin(a)] - потому что получить те же точки из штатных примитивов нельзя, т.к. признаться что там внизу черепахи до самого низа уже грешно. Нафиг так жить... хотя в итоге вещи, местами, получаются умопомрачительные.
Момент второй - странный (точнее наивный) подход к composability. Формально OpenSCAD является composable языком, но в качестве базиса этой самой composability выбрано дерево булевых операций над примитивами. Подход абсолютно естественный для математика. Действительно, объединив два дерева булевых операций еще одной, то мы получим еще одно дерево, описывающее геометрическое место точек, которые... далее по тексту. Конструкторская же мысль требует компонуемости в смысле готовых к повторному использованию запчастей.
Всё еще не очевидна проблема? Мне тоже не было очевидно, пока я не упёрся лбом. Проще пояснить на конкретном примере: допустим, у нас есть модуль детали сложносочинённой формы, допустим, кронштейн. И есть отдельно модуль крепёжного уха (прилив, чтобы дырке было в чём держаться, минус собственно дырка). Простое перечисление { кронштейн(); ухо(); ухо();... } приведет к тому, что дырки будут только в самих приливах. И это абсолютно корректно с точки зрения языка, ведь difference() с помощью которой делалась дырка в ухе, применима только к этой части дерева операций.
А непростого перечисления не предусмотрено. Обойти это можно достаточно тривиально (это даже предлагалось в виде "классного трюка" где-то на форумах или в блогах), примерно так:
difference() {
union() {
partP_body();
partQ_body();
...
partZ_body();
}
union() {
partP_holes();
partQ_holes();
...
partZ_holes();
}
}
и это практически единственный работающий способ. Проблема только в том, что ввиду примитивности языка (нет интроспекции, рефлексии, ни модули ни функции не являются first-class item'ами, никакого метапрограммирования, итд итп) эту городуху нельзя никак ни генерализовать, ни автоматизировать. Только хардкор и пальцы в кровь. Если вам попалась библиотека компонентов без учёта этого требования, то можно её спокойно выбрасывать и писать самому с нуля. В том числе еще и потому, что если автор библиотеки не предусмотрел выдачу какой-то информации наружу (например, в виде отдельных модулей, или функций), то получить её оттуда никак, только загружать в свой дизайн снаружи, "предзнанием".
Но если, по счастливому стечению обстоятельств у вас все части складываются друг на друга как кубики в лего (как зачастую происходит в тех же 3D-принтерах), то ваши волосы будут гладкими и шелковистыми.
Резюме: на самом деле абсолютно понятно откуда эти "особенности" взялись. Авторы просто однажды сказали себе: "а что если мы сделаем простой язык описания геометрических примитивов, прилепим туда несложную шаблонизацию и макры, и назовём это CAD'ом, взлетит?". Взлетело. Только нужно чётко понимать, что данном случае D обозначает "drawing", а не "design". И если вы точно знаете чего хотите, и сможете уложить это в top-down подход, то у вас всё получится. Если нет - ну, YMMV.
А если вы хотите не переписывать по двадцать раз одно и то же, а хотите писать statement'ы вида "хочу отверстия под болты такие же как во-о-о-н там, но не ближе X мм от стенки", то вообще непонятно куды бечь. Сразу в Haskell?
This entry was originally posted at
https://ex0-planet.dreamwidth.org/90504.html. Please comment there using
OpenID.