New Project

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=] ... }
  • project is the one root node. units=mm is the only accepted value (units=in changes only the viewer's readout). front= says which world direction is the piece's front (default neg_y). collection= selects a collection (see reuse-and-collections).
  • params may appear at three scopes, and the inner shadows the outer: project, component (defaults, overridable per instance) and assembly (local working values).
  • A parameter is one line: name <default> min= max= step= label= category= unit_display=. Give every user-facing dimension min/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.
  • meta is a block of free-form nodes inside project, 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 writeIt means
19.0, glue_clearance 0.2a literal number
"width / 2.0" (a quoted string in a value position)a CEL expression
normal=pos_x, edges top_perimeter, part Facea 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>" ...]
  • inverse is ordinary CEL, evaluated with the current params in scope plus the reserved name v (the requested value). The tie rule is yours to write: floor(x + 0.5) rounds ties up (the builtin round is ties-to-even). The result is clamped, then snapped to the target's own min/max/step, so --set shelf_h=900 on 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, and ww build f.kdl --set total_h=900 --for total_h=other_param picks 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 bare name value lines only — no min=/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 --set already 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) and units= anything but mm are rejected by kdl-check.
  • A typo in an identifier ("heigth - top_th") is a hard error, not a silent dead reference: kdl-check resolves 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 params is a name: edges top_perimeter is 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 (min max step label category unit_display).
  • Hitting a limit: split a deeply nested repeat (repeat 64 inside repeat 64 is 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 (preset blocks).
  • quickstart.md for a first compiling file; components-and-stock.md for 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)"

Settings

Region

A region picks sensible defaults; change any of these separately.

Units

In inches, type lengths the way you say them: 34 1/2, 3/4", 1'-2" or 12.7mm. A length that can't be read is flagged, never rounded to something else.

Paper
Date order
Clock