Project & Params#
The project root, parameters, and CEL expressions
Names: project units meta params preset label category unit_display solves inverse
When to use#
Every file starts here: the project root, the params that make the design parametric, and
the expression language that every quoted string value is written in. Read this before any
other family, because the quoting rule below decides how every other node is spelled.
Syntax#
project name="..." units=mm [schema_version=1] [front=<pos_x|neg_x|pos_y|neg_y|pos_z|neg_z>]
[collection=<Name>] { ... }
params { <name> <default> [min= max= step= label= category= unit_display=] ... }
projectis the one root node.units=mmis the only accepted value (units=inchanges only the viewer's readout).front=says which world direction is the piece's front (defaultneg_y).collection=selects a collection (seereuse-and-collections).paramsmay appear at three scopes, and the inner shadows the outer:project,component(defaults, overridable per instance) andassembly(local working values).- A parameter is one line:
name <default> min= max= step= label= category= unit_display=. Give every user-facing dimensionmin/max/step/label/category; a derived value gets none (it is not a slider).unit_display="in"makes the viewer show fractional inches. To write a default in fractional inches use(in)"1-3/4"(ADR-0074), e.g.w (in)"34 1/2" min=(in)12 max=(in)96. metais a block of free-form nodes insideproject, parsed and kept. It carries no geometry and nothing reads it at compile time.
The quoting rule (ADR-0004), which governs the whole language:
| You write | It means |
|---|---|
19.0, glue_clearance 0.2 | a literal number |
"width / 2.0" (a quoted string in a value position) | a CEL expression |
normal=pos_x, edges top_perimeter, part Face | a name or enum word, never quoted |
"'danish oil'" | a CEL expression that evaluates to a string literal |
All dimensions are floats (19.0, never 19). Z is negative going down from a part's top
face. Unit literals: (in)34.5 is 876.3 mm at parse time, (mm)N is the identity, anything
else ((ft)3) is ww::dsl::unknown_unit; in a CEL string use inch(0.75).
CEL (degrees, not radians). Functions: sin cos tan asin acos atan atan2, floor ceil round abs, min max, sqrt pow, inch, and the loudspeaker set ac_sealed_fc ac_sealed_qtc ac_port_length ac_helmholtz_fb (litres, Hz, mm; speaker-analysis). Operators: + - * /, unary -, comparisons,
&& || !, cond ? a : b; + joins text. Nothing else of CEL: map, filter, all,
exists, has, size, matches, the type-conversion functions, method calls, in and
list literals are refused with ww::eval::not_in_language.
Limits (the diagnostic names the one you hit): expression source 4 KiB, 128
operators/brackets per expression, text 4 KiB, integer arithmetic +-2^53; and the whole-file
limits: source 256 KiB, nesting depth 64, 2,000 parts, 1,000 features per part, one repeat
4,096 iterations.
Example#
project name="Test Shelf" units=mm schema_version=1 {
params {
width 600.0 min=300.0 max=1200.0 step=10.0 label="Width" category="Envelope"
depth 250.0 min=150.0 max=400.0 step=10.0 label="Depth" category="Envelope"
thick 19.0 min=12.0 max=32.0 step=1.0 label="Thickness" category="Stock"
half_w "width / 2.0" // derived: quoted, no slider
angle_rise "atan2(100.0, width) + sin(30.0)" // degrees
gap (in)0.125 // unit literal
finish_name "'danish oil'" // a string literal, via CEL
}
materials { material WhiteOak type=hardwood }
assembly Root {
part Board material=WhiteOak role=shelf {
stock WhiteOak width depth thick
by top_sw
pos x=0.0 y=0.0 z=0.0
}
}
}
Linked parameters (ADR-0035) — a slider for a derived value. A customer asks "how tall?", the model is driven by "how many shelves". A derived project param may name the literal param it solves and an inverse that turns a requested value into that param's input:
<derived> "<forward expr>" [min= max= step= label= category=] solves=<literal_param> inverse="<expr using v>"
[inverse_for_<other_param>="<expr using v>" ...]
inverseis ordinary CEL, evaluated with the current params in scope plus the reserved namev(the requested value). The tie rule is yours to write:floor(x + 0.5)rounds ties up (the builtinroundis ties-to-even). The result is clamped, then snapped to the target's ownmin/max/step, so--set shelf_h=900on a 5-shelf-max case lands on a real count.- The forward expression must depend on the target, the target must be a literal param (never derived), and the inverse must not read the derived param it sets. Cycles are errors.
- Several inverses:
inverse_for_<param>=; the first declared is the default, andww build f.kdl --set total_h=900 --for total_h=other_parampicks another. - A request lands as the target's value; the compile reports
requested -> resolved, and identity (share link, cache key) is the canonical target value, never the derived one. Setting the derived param and its target in one request is refused.
project name="Shelving" units=mm schema_version=1 {
params {
shelf_count 3.0 min=2.0 max=6.0 step=1.0 label="Shelves" category="Envelope"
gap 300.0 min=250.0 max=350.0 step=10.0 label="Shelf Gap" category="Envelope"
thick 19.0 min=12.0 max=32.0 step=1.0 label="Thickness" category="Stock"
total_h "(shelf_count * gap) + thick" min=500.0 max=2200.0 step=10.0 \
label="Height" category="Envelope" \
solves=shelf_count inverse="floor((v - thick) / gap + 0.5)"
}
materials { material WhiteOak type=hardwood }
assembly Root {
part Board material=WhiteOak role=shelf {
stock WhiteOak 600.0 250.0 thick
by top_sw
pos x=0.0 y=0.0 z=0.0
}
}
}
Named parameter bundles — preset (#642, ADR-0084). A preset "Name" { … } node,
sibling to params, bundles a sparse set of literal-parameter overrides under a display
name; the viewer shows each one as a clickable card:
preset "<Display Name>" { <param> <value> ... }
params {
case_w 800.0 min=420.0 max=1200.0 step=10.0 label="Width" category="Envelope"
height 1700.0 min=500.0 max=1900.0 step=10.0 label="Height" category="Envelope"
}
preset "Tallboy" { case_w 800.0 height 1700.0 }
preset "Bedside Chest" { case_w 500.0 height 620.0 }
- The node's own argument is the display name, same convention as
component/assembly. The body holds barename valuelines only — nomin=/max=/step=/label=, since an override is a value, not a second slider declaration. - Any subset of the project's declared parameters; a parameter the preset does not mention
is left alone. A target must be a literal parameter, never derived (
ww::preset::derived, same rule as--set). - Checked at compile time against the target's own
min=/max=/step=, reusing the same validation--setalready goes through:ww::preset::unknown/::derived/::type/::range/::step, each naming which preset. A compile error, not a warning. Two presets may not share a name (ww::preset::duplicate). Declaration order is display order.
Pitfalls and diagnostics#
- A bare integer dimension (
19) andunits=anything butmmare rejected bykdl-check. - A typo in an identifier (
"heigth - top_th") is a hard error, not a silent dead reference:kdl-checkresolves every identifier in every expression and ignores only the contents of CEL string literals. - A literal word where an expression is expected (
finish="danish") reads as an unknown identifier. Write"'danish'". - A bare argument outside
paramsis a name:edges top_perimeteris never an expression. A named property whose key is a label/enum slot (label,category,id,name,instance,role,material,type,slope,profile,section,normal,axis,joint,male,female,stop,keep,match, ...) is also never an expression. - A parameter with children (
width 1.0 { ... }) or more than one default is a parse error; an unknown key on a parameter lists the allowed ones (minmaxsteplabelcategoryunit_display). - Hitting a limit: split a deeply nested
repeat(repeat 64insiderepeat 64is 4,096 and is refused before the inner loop unrolls) or hoist an expression into a named param.
See also#
docs/KDL_AUTHORING.md§1-4 (units, Z, params,preset), §13 (limits), §16 (unit literals).docs/adr/0004(string = expression),docs/adr/0005(CEL),docs/adr/0021,docs/adr/0084(presetblocks).quickstart.mdfor a first compiling file;components-and-stock.mdfor what goes in a project next.
Names#
Every name this page declares, how the example library uses it, and the shortest real use.
project#
node in 116 of 116 examples (116 uses).
units#
word-valued property; property in 116 of 116 examples (116 uses).
meta#
project-level node; not used by any example yet.
params#
project-level node, feature; node in 116 of 116 examples (386 uses).
Shortest real use, from high_back_ladder_chair.kdl:
params { h 30.0 }
preset#
project-level node; node in 4 of 116 examples (10 uses).
Shortest real use, from shaker_trestle_dining_table.kdl:
preset "Dining Table" {
top_len 1800.0
top_w 840.0
}
label#
word-valued property; property in 116 of 116 examples (2144 uses).
Shortest real use, from drawer_stack.kdl:
width 400.0 min=250.0 max=900.0 step=10.0 label="Drawer width"
category#
word-valued property; property in 115 of 116 examples (2105 uses).
Shortest real use, from scandinavian_birch_shelving_unit.kdl:
bays 3.0 min=3.0 max=5.0 step=1.0 label="Bays" category="Envelope"
unit_display#
word-valued property; not used by any example yet.
solves#
word-valued property; property in 1 of 116 examples (1 uses).
Shortest real use, from filing_cabinet_vertical.kdl:
overall_h "leg_len + top_th" min=650.0 max=2000.0 step=5.0 \
label="Overall Height" category="Envelope" \
solves=drawer_count \
inverse="floor((v - top_th - bank_floor_h - rail_h) / opening_pitch + 0.5)"
inverse#
property; property in 1 of 116 examples (1 uses).
Shortest real use, from filing_cabinet_vertical.kdl:
overall_h "leg_len + top_th" min=650.0 max=2000.0 step=5.0 \
label="Overall Height" category="Envelope" \
solves=drawer_count \
inverse="floor((v - top_th - bank_floor_h - rail_h) / opening_pitch + 0.5)"