Как передать функцию в качестве параметра модулю OpenSCAD?

В последние несколько дней меня заинтересовала идея использования программного обеспечения на основе языка программирования для создания 3D-моделей. Одним из языков, с которыми я играл, является OpenSCAD, который оказался чрезвычайно полезным для создания интересных форм.

В настоящее время я пытаюсь создать цветок с помощью OpenSCAD, и я столкнулся с проблемой, которую мне не удалось обойти с помощью документации или других ресурсов, которые я нашел в Интернете.

Вот краткая форма вопроса:

Можно ли передать функцию в качестве параметра в модуль OpenSCAD?

Если да, то как? Если нет, то почему и что я могу сделать вместо этого?

Это подводит меня к развернутой форме вопроса с учетом особенностей моей ситуации:

Я пытаюсь создать лепестки, используя линейную экструзию 2D-полярной функции и пересекая ее с 3D-функцией.

Для этого я начал с двух очень хороших модулей, которые нашел на http://spolearninglab.com/curriculum/lessonPlans/hacking/resources/software/3d/openscad/openscad_math.html. Я не утверждаю, что написал их в первую очередь.

Первый - 3D-плоттер Дэна Ньюмана /* 3Dplot.scad */

// 3dplot -- the 3d surface generator    
// x_range -- 2-tuple [x_min, x_max], the minimum and maximum x values
// y_range -- 2-tuple [y_min, y_max], the minimum and maximum y values

// grid -- 2-tuple [grid_x, grid_y] indicating the number of grid cells along the x and y axes

// z_min -- Minimum expected z-value; used to bound the underside of the surface

// dims -- 2-tuple [x_length, y_length], the physical dimensions in millimeters

//Want to pass in z(x,y) as parameter

module 3dplot(x_range=[-10, +10], y_range=[-10,10], grid=[50,50], z_min=-5, dims=[80,80]){
    dx = ( x_range[1] - x_range[0] ) / grid[0];
    dy = ( y_range[1] - y_range[0] ) / grid[1];

// The translation moves the object so that its center is at (x,y)=(0,0)
// and the underside rests on the plane z=0

scale([dims[0]/(max(x_range[1],x_range[0])-min(x_range[0],x_range[1])),
       dims[1]/(max(y_range[1],y_range[0])-min(y_range[0],y_range[1])),1])
translate([-(x_range[0]+x_range[1])/2, -(y_range[0]+y_range[1])/2, -z_min])
union()
{
    for ( x = [x_range[0] : dx  : x_range[1]] )
    {
        for ( y = [y_range[0] : dy : y_range[1]] )
        {
            polyhedron(points=[[x,y,z_min], [x+dx,y,z_min], [x,y,z(x,y)], [x+dx,y,z(x+dx,y)],
                               [x+dx,y+dy,z_min], [x+dx,y+dy,z(x+dx,y+dy)]],
                       faces=prism_faces_1);
            polyhedron(points=[[x,y,z_min], [x,y,z(x,y)], [x,y+dy,z_min], [x+dx,y+dy,z_min],
                               [x,y+dy,z(x,y+dy)], [x+dx,y+dy,z(x+dx,y+dy)]],
                       faces=prism_faces_2);
            }
        }
    }
}

Второй — 2D Grapher /* 2dgraphing.scad */

// function to convert degrees to radians
function d2r(theta) = theta*360/(2*pi);

// These functions are here to help get the slope of each segment, and use that to find points for a correctly oriented polygon
function diffx(x1, y1, x2, y2, th) = cos(atan((y2-y1)/(x2-x1)) + 90)*(th/2);
function diffy(x1, y1, x2, y2, th) = sin(atan((y2-y1)/(x2-x1)) + 90)*(th/2);
function point1(x1, y1, x2, y2, th) = [x1-diffx(x1, y1, x2, y2, th), y1-diffy(x1, y1, x2, y2, th)];
function point2(x1, y1, x2, y2, th) = [x2-diffx(x1, y1, x2, y2, th), y2-diffy(x1, y1, x2, y2, th)];
function point3(x1, y1, x2, y2, th) = [x2+diffx(x1, y1, x2, y2, th), y2+diffy(x1, y1, x2, y2, th)];
function point4(x1, y1, x2, y2, th) = [x1+diffx(x1, y1, x2, y2, th), y1+diffy(x1, y1, x2, y2, th)];
function polarX(theta) = cos(theta)*r(theta);
function polarY(theta) = sin(theta)*r(theta);

module nextPolygon(x1, y1, x2, y2, x3, y3, th) {
    if((x2 > x1 && x2-diffx(x2, y2, x3, y3, th) < x2-diffx(x1, y1, x2, y2, th) || (x2 <= x1 && x2-diffx(x2, y2, x3, y3, th) > x2-diffx(x1, y1, x2, y2, th)))) {
        polygon(
            points = [
                point1(x1, y1, x2, y2, th),
                point2(x1, y1, x2, y2, th),
                // This point connects this segment to the next
                point4(x2, y2, x3, y3, th),
                point3(x1, y1, x2, y2, th),
                point4(x1, y1, x2, y2, th)
            ],
            paths = [[0,1,2,3,4]]
        );
    }
    else if((x2 > x1 && x2-diffx(x2, y2, x3, y3, th) > x2-diffx(x1, y1, x2, y2, th) || (x2 <= x1 && x2-diffx(x2, y2, x3, y3, th) < x2-diffx(x1, y1, x2, y2, th)))) {
        polygon(
            points = [
                point1(x1, y1, x2, y2, th),
                point2(x1, y1, x2, y2, th),
                // This point connects this segment to the next
                point1(x2, y2, x3, y3, th),
                point3(x1, y1, x2, y2, th),
                point4(x1, y1, x2, y2, th)
            ],
            paths = [[0,1,2,3,4]]
        );
    }
    else {
        polygon(
            points = [
                point1(x1, y1, x2, y2, th),
                point2(x1, y1, x2, y2, th),
                point3(x1, y1, x2, y2, th),
                point4(x1, y1, x2, y2, th)
            ],
            paths = [[0,1,2,3]]
        );
    }
}

module 2dgraph(bounds=[-10,10], th=2, steps=10, polar=false, parametric=false) {

    step = (bounds[1]-bounds[0])/steps;
    union() {
        for(i = [bounds[0]:step:bounds[1]-step]) {
            if(polar) {
                nextPolygon(polarX(i), polarY(i), polarX(i+step), polarY(i+step), polarX(i+2*step), polarY(i+2*step), th);
            }
            else if(parametric) {
                nextPolygon(x(i), y(i), x(i+step), y(i+step), x(i+2*step), y(i+2*step), th);
            }
            else {
                nextPolygon(i, f(i), i+step, f(i+step), i+2*step, f(i+2*step), th);
            }
        }
    }
}

Мой код оболочки:

include <2dgraphing.scad>;
include <3dplot.scad>;

function z(x,y) = pow(x,2)+pow(y,2); //function used in 3dplot
function r(theta) = cos(4*theta); //function used in 2dgraph

module Petals () {
    difference () {
        union () { //everything to add
            intersection () {
                3dplot([-4,4],[-4,4],[50,50],-2.5);
                scale([20, 20, 20]) linear_extrude(height=0.35)
                    2dgraph([0, 720], 0.1, steps=160, polar=true);
            }
        }
        union () { //everything to subtract

        }
    }

}

Petals();

И с миром все в порядке, когда я визуализирую самые вычислительно затратные лепестки в мире.

[Здесь я бы разместил изображение, но поскольку это мой первый пост, у меня нет необходимых 10 очков репутации]

Однако теперь я хочу вычесть лишнее с нижней части лепестков. Таким образом, я мог бы использовать 3D-график с более крутой функцией и более низкой начальной точкой и вычесть это из исходного 3D-графика.

Итак, в одной и той же программе я хочу использовать две разные функции для двух разных применений модуля 3Dplot.

Я попытался изменить 3dplot и свой код, чтобы сделать это:

Modified 3dplot:

module 3dplot(x_range=[-10, +10], y_range=[-10,10], grid=[50,50], z_min=-5, dims=[80,80], input_function)
{
    dx = ( x_range[1] - x_range[0] ) / grid[0];
    dy = ( y_range[1] - y_range[0] ) / grid[1];

    // The translation moves the object so that its center is at (x,y)=(0,0)
    // and the underside rests on the plane z=0

    scale([dims[0]/(max(x_range[1],x_range[0])-min(x_range[0],x_range[1])),
           dims[1]/(max(y_range[1],y_range[0])-min(y_range[0],y_range[1])),1])
    translate([-(x_range[0]+x_range[1])/2, -(y_range[0]+y_range[1])/2, -z_min])
    union()
    {
        for ( x = [x_range[0] : dx  : x_range[1]] )
        {
            for ( y = [y_range[0] : dy : y_range[1]] )
            {
                polyhedron(points=[[x,y,z_min], [x+dx,y,z_min], [x,y,input_function(x,y)], [x+dx,y,input_function(x+dx,y)],
                                   [x+dx,y+dy,z_min], [x+dx,y+dy,input_function(x+dx,y+dy)]],
                           faces=prism_faces_1);
                polyhedron(points=[[x,y,z_min], [x,y,input_function(x,y)], [x,y+dy,z_min], [x+dx,y+dy,z_min],
                                   [x,y+dy,input_function(x,y+dy)], [x+dx,y+dy,input_function(x+dx,y+dy)]],
                           faces=prism_faces_2);
            }
        }
    }
}

Изменил мой код:

include <2dgraphing.scad>;
include <3dplot.scad>;

function z1(x,y) = pow(x,2)+pow(y,2); //function used in 3dplot
function z2(x,y) = pow(pow(x,2)+pow(y,2),1.5)-1; //function to be subtracted out
function r(theta) = cos(4*theta); //function used in 2dgraph

module Petals () {
    difference () {
        union () { //everything to add
            intersection () {
                3dplot([-4,4],[-4,4],[50,50],-2.5);
                scale([20, 20, 20]) linear_extrude(height=0.35)
                    2dgraph([0, 720], 0.1, steps=160, polar=true, input_function=z1);
            }
        }
        union () { //everything to subtract
            3dplot([-4,4],[-4,4],[50,50],-2.5,input_function=z2);
        }
    }

}

Petals();

Я получил следующую ошибку: ПРЕДУПРЕЖДЕНИЕ: Игнорирование неизвестной функции 'input_function'.

Итак, как мне передать функцию в качестве параметра?

До этого я не писал на каком-либо функциональном языке, но, насколько я понял из Руководства пользователя OpenSCAD, «Начиная с версии 2015.03 переменные теперь могут быть назначены в любой области». Поэтому я должен иметь возможность изменять значение input_function для каждого запуска 3dplot, как и переменные в 3dplot. Я неправильно интерпретирую это?

И в качестве дополнительного вопроса: есть ли в OpenSCAD четкий способ достижения моих текущих целей без создания массивной вычислительной нагрузки в процессе рендеринга?

Я потратил достаточно времени, пытаясь решить эту проблему, поэтому публикую этот длинный вопрос, и я прошу прощения, если я затуманил простое существующее решение. Я очень ценю всех, кто готов помочь.


person scottviteri    schedule 14.12.2015    source источник
comment
Для предположительно функционального языка языку OpenSCAD не хватает базовых функциональных возможностей, таких как сопоставление с образцом, функции как передаваемые значения и т. д. Это очень расстраивает. Вы не можете назначать переменные во внешней области, но вы также не можете передавать блоки или функции...   -  person Craig Ringer    schedule 15.10.2019
comment
Есть так много вещей, которые восхитительно функциональны в opencad, но не рассматривают функции как первоклассные объекты, а другие вещи демонстрируют небольшое отсутствие предвидения... они кажутся такими простыми, чтобы быть реализованными, учитывая другие ограничения которые на месте.   -  person Chris Cogdon    schedule 14.05.2020


Ответы (2)


Передача функции в качестве параметра в настоящее время невозможна. Кроме того, генерация огромного количества мелких объектов (например, многогранников в модуле 3dplot) сильно замедлит рендеринг модели. Для этого конкретного варианта использования существуют другие варианты создания модели.

Новые функции генерации списков, доступные в последних версиях OpenSCAD, позволяют генерировать один многогранник на основе функции.

См. 3d-functions.scad в демо репозиторий. Это строит функцию f(x, y).

person Torsten Paul    schedule 15.12.2015

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

person Alexander Pruss    schedule 08.01.2019