Files
cadSet/text-to-cad/skills/implicit-cad/scripts/lib/implicit-cad.mjs
T
2026-07-22 19:38:36 +08:00

395 lines
13 KiB
JavaScript

import { IMPLICIT_CAD_SCHEMA } from "../packages/implicitjs/src/lib/implicitCad/schema.js";
export const SCHEMA = IMPLICIT_CAD_SCHEMA;
export const PI = Math.PI;
export const TWO_PI = Math.PI * 2;
export const SQRT2 = Math.sqrt(2);
export const SQRT3 = Math.sqrt(3);
function numberLiteral(value) {
const numeric = Number(value);
if (!Number.isFinite(numeric)) {
throw new Error(`Expected finite number, got ${value}`);
}
if (Object.is(numeric, -0)) {
return "0.0";
}
return Number.isInteger(numeric) ? `${numeric}.0` : String(numeric);
}
function componentList(value, dimension) {
if (typeof value === "number") {
return Array.from({ length: dimension }, () => numberLiteral(value));
}
if (!Array.isArray(value) && !ArrayBuffer.isView(value)) {
throw new Error(`Expected vec${dimension} array or scalar`);
}
return Array.from({ length: dimension }, (_, index) => numberLiteral(value[index] ?? 0));
}
export function vec2(value, y = null) {
const components = y === null ? componentList(value, 2) : [numberLiteral(value), numberLiteral(y)];
return `vec2(${components.join(", ")})`;
}
export function vec3(value, y = null, z = null) {
const components = y === null && z === null
? componentList(value, 3)
: [numberLiteral(value), numberLiteral(y), numberLiteral(z)];
return `vec3(${components.join(", ")})`;
}
export function expr(value) {
if (value && typeof value === "object" && typeof value.expr === "string") {
return value.expr;
}
if (typeof value === "number") {
return numberLiteral(value);
}
return String(value);
}
export function glsl(strings, ...values) {
return strings.reduce((result, part, index) => (
`${result}${part}${index < values.length ? expr(values[index]) : ""}`
), "");
}
function reduceCall(values, callName, radius = null) {
const parts = values.map(expr);
if (parts.length === 0) {
throw new Error(`${callName} requires at least one field`);
}
if (parts.length === 1) {
return parts[0];
}
return parts.slice(1).reduce((acc, value) => (
radius === null
? `${callName}(${acc}, ${value})`
: `${callName}(${acc}, ${value}, ${numberLiteral(radius)})`
), parts[0]);
}
function reduceCallWithArgs(values, callName, args = []) {
const parts = values.map(expr);
if (parts.length === 0) {
throw new Error(`${callName} requires at least one field`);
}
if (parts.length === 1) {
return parts[0];
}
const suffix = args.length > 0 ? `, ${args.map(expr).join(", ")}` : "";
return parts.slice(1).reduce((acc, value) => `${callName}(${acc}, ${value}${suffix})`, parts[0]);
}
export function linearMap(value, inMin, inMax, outMin, outMax) {
return `implicit_linear_map(${expr(value)}, ${numberLiteral(inMin)}, ${numberLiteral(inMax)}, ${numberLiteral(outMin)}, ${numberLiteral(outMax)})`;
}
export function ramp(value, inMin, inMax, outMin, outMax) {
return `implicit_ramp(${expr(value)}, ${numberLiteral(inMin)}, ${numberLiteral(inMax)}, ${numberLiteral(outMin)}, ${numberLiteral(outMax)})`;
}
export function twoBodyField(a, b) {
return `implicit_two_body_field(${expr(a)}, ${expr(b)})`;
}
export function twoBodyPolar(a, b, angle) {
return `implicit_two_body_polar(${expr(a)}, ${expr(b)}, ${numberLiteral(angle)})`;
}
export function triangleWaveEven(value, period) {
return `implicit_triangle_wave_even(${expr(value)}, ${expr(period)})`;
}
export function triangleWaveEvenPositive(value, period) {
return `implicit_triangle_wave_even_positive(${expr(value)}, ${expr(period)})`;
}
export function triangleWaveOdd(value, period) {
return `implicit_triangle_wave_odd(${expr(value)}, ${expr(period)})`;
}
export function triangleWaveOddPositive(value, period) {
return `implicit_triangle_wave_odd_positive(${expr(value)}, ${expr(period)})`;
}
export function unionSharp(values) {
return reduceCall(values, "implicit_union_sharp");
}
export function intersectSharp(values) {
return reduceCall(values, "implicit_intersect_sharp");
}
export function unionRound(values, radius = 0) {
return reduceCall(values, "implicit_union_round", radius);
}
export function intersectRound(values, radius = 0) {
return reduceCall(values, "implicit_intersect_round", radius);
}
export function unionChamfer(values, radius = 0) {
return reduceCall(values, "implicit_union_chamfer", radius);
}
export function intersectChamfer(values, radius = 0) {
return reduceCall(values, "implicit_intersect_chamfer", radius);
}
export function unionExp(values, radius = 0) {
return reduceCall(values, "implicit_union_exp", radius);
}
export function intersectExp(values, radius = 0) {
return reduceCall(values, "implicit_intersect_exp", radius);
}
export function unionLpNorm(values, radius = 0, normPower = 2) {
return reduceCallWithArgs(values, "implicit_union_lp_norm", [numberLiteral(radius), numberLiteral(normPower)]);
}
export function intersectLpNorm(values, radius = 0, normPower = 2) {
return reduceCallWithArgs(values, "implicit_intersect_lp_norm", [numberLiteral(radius), numberLiteral(normPower)]);
}
export function unionRvachev(values, radius = 0) {
return reduceCall(values, "implicit_union_rvachev", radius);
}
export function intersectRvachev(values, radius = 0) {
return reduceCall(values, "implicit_intersect_rvachev", radius);
}
export function booleanSharp() {
return { union: unionSharp, intersect: intersectSharp };
}
export function booleanRound(radius = 0) {
return {
radius,
union: (values) => unionRound(values, radius),
intersect: (values) => intersectRound(values, radius),
};
}
export function booleanExp(radius = 0) {
return {
radius,
union: (values) => unionExp(values, radius),
intersect: (values) => intersectExp(values, radius),
};
}
export function booleanLp(normPower, radius = 0) {
return {
radius,
normPower,
union: (values) => unionLpNorm(values, radius, normPower),
intersect: (values) => intersectLpNorm(values, radius, normPower),
};
}
export function booleanRvachev(radius = 0) {
return {
radius,
union: (values) => unionRvachev(values, radius),
intersect: (values) => intersectRvachev(values, radius),
};
}
export function booleanChamfer(radius = 0) {
return {
radius,
union: (values) => unionChamfer(values, radius),
intersect: (values) => intersectChamfer(values, radius),
};
}
export function difference(target, tools, {
union = unionSharp,
intersect = intersectSharp
} = {}) {
const toolList = Array.isArray(tools) ? tools : [tools];
const toolUnion = union(toolList);
return intersect([target, `-(${toolUnion})`]);
}
export function sphere(p, center, radius) {
return `implicit_sphere(${expr(p)}, ${vec3(center)}, ${numberLiteral(radius)})`;
}
export function circle(p, center, radius) {
return sphere(p, center, radius);
}
export function boxCentered(p, size, { center = [0, 0, 0] } = {}) {
return `implicit_box_centered(${expr(p)}, ${vec3(size)}, ${vec3(center)})`;
}
export function plane(p, origin, normal) {
return `implicit_plane(${expr(p)}, ${vec3(origin)}, ${vec3(normal)})`;
}
export function lineSegment(p, endpointA, endpointB) {
return `implicit_line_segment(${expr(p)}, ${vec3(endpointA)}, ${vec3(endpointB)})`;
}
export function torus(p, majorRadius, minorRadius) {
return `implicit_torus(${expr(p)}, ${numberLiteral(majorRadius)}, ${numberLiteral(minorRadius)})`;
}
export function axis(p, origin, direction) {
return `implicit_axis(${expr(p)}, ${vec3(origin)}, ${vec3(direction)})`;
}
export function cylinder(p, origin, direction, radius) {
return `implicit_cylinder(${expr(p)}, ${vec3(origin)}, ${vec3(direction)}, ${numberLiteral(radius)})`;
}
export function cylinderCapped(p, endpointA, endpointB, radius) {
return `implicit_cylinder_capped(${expr(p)}, ${vec3(endpointA)}, ${vec3(endpointB)}, ${numberLiteral(radius)})`;
}
export function capsule(p, endpointA, endpointB, radius) {
return `implicit_capsule(${expr(p)}, ${vec3(endpointA)}, ${vec3(endpointB)}, ${numberLiteral(radius)})`;
}
export function coneCapsule(p, endpointA, endpointB, radiusA, radiusB) {
return `implicit_cone_capsule(${expr(p)}, ${vec3(endpointA)}, ${vec3(endpointB)}, ${numberLiteral(radiusA)}, ${numberLiteral(radiusB)})`;
}
export function cone(p, apex, direction, halfAngle) {
return `implicit_cone(${expr(p)}, ${vec3(apex)}, ${vec3(direction)}, ${numberLiteral(halfAngle)})`;
}
export function coneCapped(p, endpointA, endpointB, radiusA, radiusB) {
return `implicit_cone_capped(${expr(p)}, ${vec3(endpointA)}, ${vec3(endpointB)}, ${numberLiteral(radiusA)}, ${numberLiteral(radiusB)})`;
}
export function shell(distance, thickness, bias = 0) {
return `implicit_shell(${expr(distance)}, ${numberLiteral(thickness)}, ${numberLiteral(bias)})`;
}
export function rotateAxis(p, origin, direction, angle) {
return `implicit_rotate_axis(${expr(p)}, ${vec3(origin)}, ${vec3(direction)}, ${numberLiteral(angle)})`;
}
export function repeatCentered(p, period) {
return `implicit_repeat_centered(${expr(p)}, ${vec3(period)})`;
}
export function remapCylindrical(p, circumference) {
return `implicit_remap_cylindrical(${expr(p)}, ${numberLiteral(circumference)})`;
}
export function cubicGrid(p, size) {
return `implicit_cubic_grid(${expr(p)}, ${vec3(size)})`;
}
export function squareHoneycomb(p, size) {
return `implicit_square_honeycomb(${expr(p)}, ${vec2(size)})`;
}
export function squareHoneycombReinforced(p, size, { rotation = 0.25, rotation2 = null } = {}) {
return `implicit_square_honeycomb_reinforced(${expr(p)}, ${vec2(size)}, ${numberLiteral(rotation)}, ${numberLiteral(rotation2 ?? 0)}, ${numberLiteral(rotation2 === null ? 0 : 1)})`;
}
export function squareDiagonalHoneycomb(p, size) {
return `implicit_square_diagonal_honeycomb(${expr(p)}, ${vec2(size)})`;
}
export function octetHoneycomb(p, size) {
return `implicit_octet_honeycomb(${expr(p)}, ${vec2(size)})`;
}
export function hexagonalHoneycomb(p, size, { setback = 1 / 3 } = {}) {
const resolvedSize = typeof size === "number" ? [size, size * SQRT3] : size;
return `implicit_hexagonal_honeycomb(${expr(p)}, ${vec2(resolvedSize)}, ${numberLiteral(setback)})`;
}
export function triangularHoneycomb(p, size) {
const resolvedSize = typeof size === "number" ? [size, size * SQRT3] : size;
return `implicit_triangular_honeycomb(${expr(p)}, ${vec2(resolvedSize)})`;
}
export function tpmsGyroid(p, period, drop = [1, 1, 1]) {
return `implicit_tpms_gyroid(${expr(p)}, ${vec3(period)}, ${vec3(drop)})`;
}
export function tpmsSchwarz(p, period, { drop = [1, 1, 1], gyroidBlend = 0 } = {}) {
return `implicit_tpms_schwarz(${expr(p)}, ${vec3(period)}, ${vec3(drop)}, ${numberLiteral(gyroidBlend)})`;
}
export function tpmsDiamond(p, period, { drop = [1, 1, 1], gyroidBlend = 0 } = {}) {
return `implicit_tpms_diamond(${expr(p)}, ${vec3(period)}, ${vec3(drop)}, ${numberLiteral(gyroidBlend)})`;
}
export function tpmsLidinoid(p, period, { drop = [1, 1, 1], gyroidBlend = 0 } = {}) {
return `implicit_tpms_lidinoid(${expr(p)}, ${vec3(period)}, ${vec3(drop)}, ${numberLiteral(gyroidBlend)})`;
}
export function tpmsNeovius(p, period, { drop = [1, 1, 1], schwarzBlend = 0 } = {}) {
return `implicit_tpms_neovius(${expr(p)}, ${vec3(period)}, ${vec3(drop)}, ${numberLiteral(schwarzBlend)})`;
}
export function tpmsSplitP(p, period, { lidinoidBlend = 1, gyroidOctave = 1, schwarzOctave = 1 } = {}) {
return `implicit_tpms_split_p(${expr(p)}, ${vec3(period)}, ${numberLiteral(lidinoidBlend)}, ${numberLiteral(gyroidOctave)}, ${numberLiteral(schwarzOctave)})`;
}
export function tpmsIwp(p, period, drop = [1, 1, 1]) {
return `implicit_tpms_iwp(${expr(p)}, ${vec3(period)}, ${vec3(drop)})`;
}
export function distanceFunction(distanceExpression, { point = "p" } = {}) {
return `float sdf(vec3 ${point}) {\n return ${expr(distanceExpression)};\n}`;
}
export function colorFunction(colorExpression, { point = "p", normal = "normal" } = {}) {
return `vec3 color(vec3 ${point}, vec3 ${normal}) {\n return clamp(${expr(colorExpression)}, vec3(0.0), vec3(1.0));\n}`;
}
function distanceSource(value) {
return typeof value === "function" || /\bsdf\s*\(/.test(expr(value))
? value
: distanceFunction(value);
}
function colorSource(value) {
return typeof value === "function" || /\bcolor\s*\(/.test(expr(value))
? value
: colorFunction(value);
}
export function createImplicitModel({
name = "Implicit CAD",
description = "",
units = "mm",
bounds,
params = {},
values = {},
animations = {},
render = {},
glsl = "",
distance,
color = "",
} = {}) {
const glslSource = glsl || [
distance !== undefined ? distanceSource(distance) : "",
color ? colorSource(color) : "",
].filter(Boolean).join("\n\n");
return {
schema: SCHEMA,
name,
description,
units,
...(bounds !== undefined ? { bounds } : {}),
...(params && Object.keys(params).length ? { params } : {}),
...(values && Object.keys(values).length ? { values } : {}),
...(animations && Object.keys(animations).length ? { animations } : {}),
...(render && Object.keys(render).length ? { render } : {}),
glsl: glslSource,
};
}