8

Reference: create a ConTeXt command which takes optional key=value parameters, as well as mandatory parameters

I am trying to make a function which takes a parametric curve and inputs it to a list of segments, which will then be drawn in a separate command.

I'm getting bad argument #1 to 'cos' (number expected, got nil).

How can I correctly input the parametric equations?

% test.tex
\ctxloadluafile{test}
\starttext
  \startMPcode
    \AppendCurve[
      u_start   = 0,
      u_end     = 1,
      u_samples = 20,
      fx        = "math.cos(u)",
      fy        = "math.sin(u)",
      fz        = "0"
    ]
    \rendersegments
  \stopMPcode
\stoptext
-- test.lua
segments = {}
local settings_to_hash = utilities.parsers.settings_to_hash
local function append_curve(hash
    -- u_start
    -- ,u_end
    -- ,u_samples
    -- ,fx,fy,fz
)
    local hash = settings_to_hash(hash) or {}
    local u_start = hash.u_start or 0
    local u_end = hash.u_end or 2*math.pi
    local u_samples = hash.u_samples or 3
    local fx = hash.fx or math.cos(u)
    local fy = hash.fy or math.sin(u)
    local fz = hash.fz or 1.5 * math.sin(2 * u)


    local u_step = (u_end - u_start) / (u_samples - 1)
    local function parametric_curve(u)
        return {fx(u),fy(u),fz(u),1}
    end
    for i = 0, u_samples - 2 do
        local u = u_start + i * u_step
        local color = (u - u_start) / (u_end - u_start)
            local A = parametric_curve(u)
            local B = parametric_curve(u + u_step)
            -- the tables for surfaces have 5 values
            table.insert(segments, {A, B})
    end
end

observer = {{0,0,1,1}}
local function render_segments()
    for _, seg in ipairs(segments) do
        local S3, E3 = seg[1], seg[2]
        local Sx, Sy = Sp[1][1], Sp[1][2]
        local Ex, Ey = Ep[1][1], Ep[1][2]

        tex.print(string.format(
            'draw (%.4fcm,%.4fcm) -- (%.4fcm,%.4fcm);',
            Sx, Sy, Ex, Ey
        ))
    end
    segments = {}
end

interfaces.implement {
    name      = "rendersegments",
    actions   = render_segments,
    public = true,
    arguments = {},
}

interfaces.implement {
    name      = "AppendCurve",
    actions   = append_curve,
    public = true,
    arguments = { "optional" },
}

1 Answer 1

5

You have the following line

local fx = hash.fx or math.cos(u)

but you haven't defined u anywhere, so Lua treats it as if it were nil. There's a standard trick that you can use in Lua to prevent you from accessing undefined variables though:

local ignore_nils = table.tohash {
    "actuarian",
    "askedformat",
    "attr",
    "fix_lmmonoregular",
    "mode",
    "recursive",
    "relativeid",
    "width",
}

table.setmetatableindex(_ENV, function(t, k)
    -- ConTeXt itself tries to access the undefined variables listed in
    -- "ignore_nils", so we need to ignore these.
    if ignore_nils[k] then
        return nil
    else
        error("Undefined variable: " .. k, 2)
    end
end)

Adding this to the start of your Lua code gives the following error:

lua error       > lua error on line 96 in file ./context-lua-eval-param.tex:

registered function call [1860]: [ctxlua]:35: Undefined variable: u
stack traceback:
        [C]: in function 'error'
        [ctxlua]:19: in metamethod 'index'
        [ctxlua]:35: in function <[ctxlua]:25>
        (...tail calls...)
25     local function append_curve(hash
26         -- u_start
27         -- ,u_end
28         -- ,u_samples
29         -- ,fx,fy,fz
30     )
31         local hash = settings_to_hash(hash) or {}
32         local u_start = hash.u_start or 0
33         local u_end = hash.u_end or 2*math.pi
34         local u_samples = hash.u_samples or 3
35 >>      local fx = hash.fx or math.cos(u)
36         local fy = hash.fy or math.sin(u)
37         local fz = hash.fz or 1.5 * math.sin(2 * u)
38     
39     
40         local u_step = (u_end - u_start) / (u_samples - 1)
41         local function parametric_curve(u)
42             return {fx(u),fy(u),fz(u),1}
43         end
44         for i = 0, u_samples - 2 do
45             local u = u_start + i * u_step
mtx-context     | fatal error: return code: 1

If you add inspect(hash) to the definition of append_curve, you can see what the error is:

\startluacode
local settings_to_hash = utilities.parsers.settings_to_hash
local function append_curve(hash)
    local hash = settings_to_hash(hash) or {}
    inspect(hash)
end

interfaces.implement {
    name      = "AppendCurve",
    actions   = append_curve,
    public = true,
    arguments = { "optional" },
}
\stopluacode


\starttext
    \startMPcode
        \AppendCurve[
            u_start   = 0,
            u_end     = 1,
            u_samples = 20,
            fx        = "math.cos(u)",
            fy        = "math.sin(u)",
            fz        = "0"
        ]
    \stopMPcode
\stoptext
table={
 ["fx "]=" \"math.cos(u)\"",
 ["fy "]=" \"math.sin(u)\"",
 ["fz "]=" \"0\" ",
 ["u_end "]=" 1",
 ["u_samples "]=" 20",
 ["u_start "]=" 0",
}

This shows that you shouldn't include spaces around the equals sign (=) and you shouldn't use "quotes" around your variables.


You have quite a few other small errors in your code; correcting them all gives the following results:

\startluacode
local segments = {} -- **always** declare all variables as local

local settings_to_hash = utilities.parsers.settings_to_hash

-- Converts a string as a function of "u" to a Lua function with 1 parameter
local function string_function(str)
    if not str or str == "" then
        return nil
    end
    return load(("return function(u) return %s end"):format(str))()
end

local function append_curve(hash)
    local hash = settings_to_hash(hash) or {}

    local u_start   = hash.u_start or 0
    local u_end     = hash.u_end or 2*math.pi
    local u_samples = hash.u_samples or 3
    local fx        = string_function(hash.fx) or math.cos
    local fy        = string_function(hash.fy) or math.sin
    local fz        = string_function(hash.fz) or function(u) return 1.5 * math.sin(2 * u) end
    local color     = string_function(hash.color) or function() return "black" end

    local u_step = (u_end - u_start) / (u_samples - 1)

    local function parametric_curve(u)
        return { fx(u), fy(u), fz(u), 1 }
    end

    for i = 0, u_samples - 2 do
        local u = u_start + i * u_step
        local A = parametric_curve(u)
        local B = parametric_curve(u + u_step)
        -- the tables for surfaces have 5 values
        table.insert(segments, { A, B, color(u) })
    end
end

local observer = { { 0, 0, 1, 1 } }

local function render_segments()
    for _, seg in ipairs(segments) do
        local S3, E3 = seg[1], seg[2]
        local Sx, Sy = S3[1], S3[2]
        local Ex, Ey = E3[1], E3[2]
        local color = seg[3]

        context(
            "draw (%.4fcm,%.4fcm) -- (%.4fcm,%.4fcm) withcolor %s;",
            Sx, Sy, Ex, Ey, color
        )
    end
    segments = {}
end

interfaces.implement {
    name      = "rendersegments",
    actions   = render_segments,
    public = true,
    arguments = {},
}

interfaces.implement {
    name      = "AppendCurve",
    actions   = append_curve,
    public = true,
    arguments = { "optional" },
}
\stopluacode

\startMPpage[offset=1ex, scale=10000]
    \AppendCurve[
        u_start=0,
        u_end=6.28,
        u_samples=20,
        fx=math.cos(u),
        fy=math.sin(u),
        fz=0,
        color={u < 3.14 and "red" or "blue"}
    ]
    \rendersegments
\stopMPpage

output

2
  • I want to make string_function for two parameters, would return load(("return function(u,v) return %s end"):format(str))() work? Commented Jun 6 at 12:15
  • 1
    @Jasper Yup, that should work just fine Commented Jun 6 at 20:48

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.