// The MATLAB-syntax helper functions available to a design script, as numbl // workspace files. Each is an ordinary .m function; the design script calls // them like any other function. Everything is vectorized over grid matrices. // steelsim_field is internal: evaluatePlate uses it to sample a field. import type { WorkspaceFile } from "numbl"; const files: Record = { "inch.m": `function v = inch() % inch: one inch in meters. v = 0.0254; end `, "mm.m": `function v = mm() % mm: one millimeter in meters. v = 1e-3; end `, "circle.m": `function f = circle(diameter, hole) % circle(diameter, hole): outline of a disc with an optional center hole. if nargin < 2, hole = 0; end f = @(x, y, r, theta) (r <= diameter/2) & (r >= hole/2); end `, "ellipse.m": `function f = ellipse(width, height, hole) % ellipse(width, height, hole): outline of an ellipse with an optional center hole. if nargin < 3, hole = 0; end f = @(x, y, r, theta) ((x/(width/2)).^2 + (y/(height/2)).^2 <= 1) & (r >= hole/2); end `, "rectangle.m": `function f = rectangle(width, height, hole) % rectangle(width, height, hole): outline of a centered rectangle with an optional center hole. if nargin < 3, hole = 0; end f = @(x, y, r, theta) (abs(x) <= width/2) & (abs(y) <= height/2) & (r >= hole/2); end `, "clamp.m": `function v = clamp(x, lo, hi) % clamp(x, lo, hi): x limited to [lo, hi], elementwise. v = min(hi, max(lo, x)); end `, "lerp.m": `function v = lerp(a, b, t) % lerp(a, b, t): a at t = 0, b at t = 1, elementwise. v = a + (b - a) .* t; end `, "smoothstep.m": `function v = smoothstep(e0, e1, x) % smoothstep(e0, e1, x): 0 below e0, 1 above e1, a smooth cubic between. t = clamp((x - e0) ./ (e1 - e0), 0, 1); v = t.^2 .* (3 - 2*t); end `, "bump.m": `function v = bump(d, radius, height) % bump(d, radius, height): Gaussian bump of the given height, falling to % 1/e at the given radius; d is the distance from its center. v = height * exp(-(d.^2) / radius^2); end `, "dome.m": `function v = dome(d, radius, height) % dome(d, radius, height): cosine dome of the given height and base radius, % with zero slope at the rim and the apex; d is the distance from its center. v = 0.5 * height * (1 + cos(pi * d / radius)) .* (d < radius); end `, "bow.m": `function v = bow(s, L, sag, offset) % bow(s, L, sag, offset): parabolic bow of the given sag across a span L % centered on 0; s is the coordinate along the span, and the offset in % -1..1 moves the crest toward one end. if nargin < 4, offset = 0; end t = 2*s/L; v = sag * (1 - t.^2) .* (1 + offset*t); end `, "dents.m": `function v = dents(x, y, cx, cy, radius, height) % dents(x, y, cx, cy, radius, height): the sum of Gaussian bumps centered % at (cx(k), cy(k)) with the given radii and heights (vectors, or scalars % shared by all the dents). v = zeros(size(x)); for k = 1:numel(cx) rk = radius(min(k, numel(radius))); hk = height(min(k, numel(height))); v = v + bump(hypot(x - cx(k), y - cy(k)), rk, hk); end end `, "seededrand.m": `function u = seededrand(seed, n) % seededrand(seed, n): n uniform numbers in [0, 1) from a small seeded % generator, the same on every run and every machine. s = floor(seed); u = zeros(1, n); for k = 1:n s = mod(s * 1664525 + 1013904223, 4294967296); u(k) = s / 4294967296; end end `, "noise2d.m": `function f = noise2d(seed, len) % noise2d(seed, len): a smooth random field f(x, y) of unit RMS with the % covariance exp(-d^2 / len^2), the same for a given seed on any grid. n = 64; u = reshape(seededrand(seed, 5*n), 5, n); kx = sqrt(-2*log(1 - u(1,:))) .* cos(2*pi*u(2,:)) * sqrt(2) / max(len, 1e-4); ky = sqrt(-2*log(1 - u(3,:))) .* cos(2*pi*u(4,:)) * sqrt(2) / max(len, 1e-4); ph = 2*pi*u(5,:); f = @(x, y) noise2d_eval(kx, ky, ph, x, y); end `, "noise2d_eval.m": `function v = noise2d_eval(kx, ky, ph, x, y) v = zeros(size(x)); for k = 1:numel(kx) v = v + cos(kx(k) * x + ky(k) * y + ph(k)); end v = v / sqrt(numel(kx) / 2); end `, "steelsim_field.m": `function v = steelsim_field(f, what, x, y, r, theta) % Samples a design field (a number or a function handle) on the grid matrices. if isa(f, 'function_handle') try v = f(x, y, r, theta); catch e error('%s: %s', what, e.message); end elseif isnumeric(f) || islogical(f) v = f; else error('%s must be a number or a function handle @(x, y, r, theta)', what); end if isscalar(v) v = double(v) * ones(size(x)); elseif ~isequal(size(v), size(x)) error('%s returned a %d x %d array for a %d x %d grid; use elementwise operators (.*, ./, .^)', what, size(v, 1), size(v, 2), size(x, 1), size(x, 2)); else v = double(v); end end `, }; export const HELPER_FILES: WorkspaceFile[] = Object.entries(files).map(([name, source]) => ({ name, source })); /** The helper names a script may call (for the interface's hint). */ export const HELPER_NAMES = Object.keys(files).filter((n) => !n.startsWith("steelsim_") && !n.startsWith("noise2d_eval")).map((n) => n.replace(/\.m$/, ""));