Language User manual

Kraft: Portable DSL for Writing Readable Node Graphs

Kraft is a lightweight programming language to describe node graphs. First use case is the Blender Node System

This manual shows the syntax you use to declare nodes, connect sockets, reuse groups & import shared files.

first_material.kr
1sync23Material {4    noise = NoiseTexture(scale = 5)5    bsdf = PrincipledBSDF()6    out = MaterialOutput()7    noise.color >> bsdf.base_color8    bsdf.bsdf >> out.surface9}
Entry groupsMaterial, Geometry, Compositor, Texture
Connection arrowsoutput >> input, input << output
Valuesnumbers, strings, booleans, enums, colors

Section 1

First Steps

Write your first `.kr` file

A Kraft file describes a node graph as text. Start with a preview mode, then declare the graph you want to create.

  • Use the `.kr` extension for Kraft source.
  • Put one entry group in the file when you want Blender to preview that graph.
  • Inside the group, declare nodes first and connect their sockets after that.
  • The default preview mode is `readonly`, so leaving the directive out still keeps the script in charge.
first_material.kr
1sync23Material {4    noise = NoiseTexture(scale = 5)5    bsdf = PrincipledBSDF()6    out = MaterialOutput()7    noise.color >> bsdf.base_color8    bsdf.bsdf >> out.surface9}

Choose a preview mode

Preview mode tells Blender whether the text alone owns the graph or whether graph edits can be written back into the text.

  • `sync` lets graph changes to automaticly update the Kraft source code.
  • if not used `readonly`is the default keeps the graph locked to the last valid script.
  • Script edits are always handled first. If the text is invalid, Kraft leaves the current preview graph alone.
  • Use readonly while learning syntax, then switch to sync when you want to mix text editing with graph editing.
preview_modes.kr
1sync

Section 2

Entry Groups

Pick the graph kind

The first word of a group chooses the kind of Blender node tree Kraft will describe. These special groups are the main entry points.

  • `Material` is for shader materials — declare nodes like `PrincipledBSDF()` and `MaterialOutput()` explicitly.
  • `Geometry` is for geometry node trees — declare `GeometryOutput()` for output geometry.
  • `Compositor` is for compositor trees — declare `CompositorOutput()` for the composite output.
  • `Texture` is for texture-style node trees and can be used without a required output connection.
entry_groups.kr
1Material {2    noise = NoiseTexture(scale = 5)3    bsdf = PrincipledBSDF()4    out = MaterialOutput()5    noise.color >> bsdf.base_color6    bsdf.bsdf >> out.surface7}89Geometry {10    join = JoinGeometry()11    out = GeometryOutput()12    join.geometry >> out.geometry13}1415Compositor {16    out = CompositorOutput()17    noise = NoiseTexture(scale = 3)18    noise.color >> out.image19}2021Texture {22    noise = NoiseTexture(scale = 8)23    out = TextureOutput()24    noise.color >> out.color25}

Name reusable groups

A group with a second name is reusable. A group without a second name is an entry point for that special graph kind.

  • `Material tint { ... }` declares a reusable material-shaped group named `tint`.
  • `Material { ... }` declares the material entry group in the current file.
  • Named groups can expose inputs and outputs through boundary connections.
  • Use short names when the group behaves like a small node graph function.
named_groups.kr
1Material tint {2    mix = MixRGB(3      blend_type = MULTIPLY4    )5    new_color << mix.color1 # grp input socket created6    mix.color >> new_output # grp output socket created7}89Material {10    noise = NoiseTexture(scale = 8)11    tinted = tint(new_color << noise.color)12    bsdf = PrincipledBSDF()13    out = MaterialOutput()14    tinted.new_output >> bsdf.base_color15    bsdf.bsdf >> out.surface16}

Section 3

Nodes And Connections

Declare nodes

A node declaration gives a node a local name, then calls a node type or reusable group. Arguments configure properties and input socket defaults. An optional `@pos(x, y)` annotation stores the graph editor position.

  • Write `name = NodeType()` to create a node.
  • Use `property = value` for node properties and settable input values.
  • Place `@pos(x, y)` on the line before a node declaration to store its graph editor position.
  • A node name becomes the left side of later socket references such as `noise.color`.
nodes.kr
1Material {2    @pos(100, 200)3    noise = NoiseTexture(scale = 12)4    ramp = ColorRamp()5    mix = MixRGB(6      blend_type = ADD7      color2 = Color(51, 128, 255, 255)8    )9    bsdf = PrincipledBSDF()10    out = MaterialOutput()11    noise.factor >> ramp.input12    ramp.color >> mix.color213    noise.color >> bsdf.base_color14    bsdf.bsdf >> out.surface15}

Section 4

Groups

Expose group inputs and outputs

A reusable group can act like a node. Boundary connections decide which inputs and outputs callers can use.

  • `color << mix.color1` creates a group input named `color` and feeds `mix.color1`.
  • `mix.color >> output` creates a group output named `output`.
  • Call the group with the same argument syntax you use for built-in nodes.
  • The group body can still use regular node declarations and connections.
group_boundary.kr
1Material tint {2    mix = MixRGB(color1 << color)3    mix.color >> output4}56Material {7    noise = NoiseTexture(scale = 6)8    tinted = tint(color << noise.color)9    bsdf = PrincipledBSDF()10    out = MaterialOutput()11    tinted.output >> bsdf.base_color12    bsdf.bsdf >> out.surface13}

Use inline groups for local helpers

Use `Group { ... }` when a helper only matters inside one graph. The node name becomes the inline group's name.

  • Inline groups avoid creating a top-level named group.
  • They use the same boundary input and output rules as named groups.
  • Use them for small local transforms that make the main graph easier to read.
inline_group.kr
1Material {2    tint = Group {3        mix = MixRGB(color1 << color)4        mix.color >> output5    }67    noise = NoiseTexture(scale = 9)8    tinted = tint(color << noise.color)9    bsdf = PrincipledBSDF()10    out = MaterialOutput()11    tinted.output >> bsdf.base_color12    bsdf.bsdf >> out.surface13}

Section 5

Values

Set numbers, strings, booleans, and enums

Arguments accept the small value set used by Blender node properties and unlinked input sockets.

  • Numbers can be integers or decimals.
  • Strings use double quotes.
  • Booleans are `true` and `false`.
  • Enum values are written as bare uppercase names such as `MULTIPLY` or `FLOAT`. You can only use the names provided by that particular node.
values.kr
1Material {2    noise = NoiseTexture(scale = 18)3    switch = Switch(type = FLOAT)4    mix = MixRGB(blend_type = MULTIPLY)5    bsdf = PrincipledBSDF()6    out = MaterialOutput()78    noise.factor >> switch.true9    switch.output >> bsdf.roughness10    bsdf.bsdf >> out.surface11}

Write color values

Kraft supports common color forms so you can keep material source readable while still being precise.

  • `Color(r, g, b, a)` writes numeric channels. Integer 0 to 255 (2⁸) per channel
  • `Color.Name` can be used for named colors when the platform schema accepts them.
  • Hex colors use `#rgb`, `#rgba`, `#rrggbb`, or `#rrggbbaa`.
  • A `#` that is not a valid hex color starts a comment instead.
colors.kr
1Material {2    warm = MixRGB(color1 = Color(255, 89, 25, 255))3    cool = MixRGB(color1 = Color.Blue)4    flat = MixRGB(color1 = #ff8844)5    bsdf = PrincipledBSDF()6    out = MaterialOutput()7    warm.color >> bsdf.base_color8    bsdf.bsdf >> out.surface9}

Store values in variables

Primitive values can be stored in named variables and reused across multiple nodes in the same scope. Enums cannot be stored — they can only be passed directly as arguments.

  • `<name> = <value>` declares a variable of any primitive type: `count = 5`, `radius = 2.5`, `label = "glass"`, `enabled = true`.
  • Variables are scoped to the enclosing group and can be referenced by any node declaration or connection in that group.
  • Use a variable wherever a literal value is accepted: `NoiseTexture(scale = count)`.
  • Enums like `MULTIPLY` or `FLOAT` cannot be stored in variables — they must be written inline.
variables.kr
1Material {2    scale = 53    label = "glass"4    enabled = true56    noise = NoiseTexture(scale = scale)7    bsdf = PrincipledBSDF()8    out = MaterialOutput()9    noise.color >> bsdf.base_color10    bsdf.bsdf >> out.surface11}

Primitive Types Operators

Numbers support all standard arithmetic operators with standard operator precedence. Strings can be concatenated with `+`, and values are auto-converted to strings in concatenation. Booleans support logical inversion.

  • Arithmetic: `+`, `-`, `*`, `/`, `%` with standard precedence. Use `( )` to group: `(10 + 5) * 2`.
  • String concatenation uses `+`: `"hello" + " " + "world"` produces `"hello world"`.
  • Auto-conversion in concatenation: `"value: " + 42` produces `"value: 42"` and `"got " + true` produces `"got true"`.
  • Boolean inversion: `!true` is `false`, `!enabled` flips a boolean variable.
  • Variables and literal values can be mixed freely: `total / 3` or `"result: " + total`.
operations.kr
1Material {2    total = (10 + 5) * 2 / 33    msg = "result: " + total + " " + true4    flipped = !false56    noise = NoiseTexture(scale = total)7    bsdf = PrincipledBSDF()8    out = MaterialOutput()9    noise.color >> bsdf.base_color10    bsdf.bsdf >> out.surface11}

Section 6

Built-in Math Functions

Mathematical functions and constants

Kraft provides a comprehensive set of math functions and constants for use in expressions. These can be called anywhere a numeric value is expected.

  • All functions return a float value.
  • Constants `PI`, `TAU`, `INF`, and `NAN` are predefined.
  • Functions compose: `pow(sin(x), 2) + pow(cos(x), 2)`.
math_functions.kr
1abs(x) // Absolute value of x.2sign(x) // -1, 0, or 1 depending on the sign of x.3min(a, b) // The smaller of two values.4max(a, b) // The larger of two values.5clamp(value, min, max) // Restrict a value to a range.6snapped(value, step) // Round to the nearest multiple of step.7floor(x) // Round down to nearest integer.8ceil(x) // Round up to nearest integer.9round(x) // Round to nearest integer.10fmod(a, b) // Floating-point remainder of division.11posmod(a, b) // Positive modulo result.12pow(base, exponent) // Raise a number to a power.13sqrt(x) // Square root of x.14exp(x) // e raised to the power of x.15log(x) // Natural logarithm of x.16sin(x) // Sine of an angle in radians.17cos(x) // Cosine of an angle in radians.18tan(x) // Tangent of an angle in radians.19asin(x) // Inverse sine in radians.20acos(x) // Inverse cosine in radians.21atan(x) // Inverse tangent in radians.22atan2(y, x) // Angle from X-axis to point (x, y).23deg_to_rad(degrees) // Convert degrees to radians.24rad_to_deg(radians) // Convert radians to degrees.25lerp(from, to, weight) // Linear interpolation.26inverse_lerp(from, to, val) // Interpolation factor within a range.27remap(v, i_start, i_end, o_start, o_end) // Map a value from one range to another.28smoothstep(from, to, value) // Smooth interpolation.29is_equal_approx(a, b) // Approximately equal check.30is_zero_approx(x) // Approximately zero check.31is_nan(x) // NaN check.32is_inf(x) // Infinity check.33wrapf(value, min, max) // Wrap a float into a range.34wrapi(value, min, max) // Wrap an integer into a range.35PI // 3.14159265...36TAU // 6.28318531...37INF // Positive infinity.38NAN // Not a Number.

Section 7

Files And Comments

Import another Kraft file

Use imports when a workspace has shared groups or when the active entry point lives in another file.

  • `from "./palette.kr" as palette` binds another file to an alias.
  • `entry palette.Material` selects an imported special group as the active entry.
  • Imported groups can be called with qualified names such as `palette.WarmTint()`.
  • Keep aliases short and descriptive so connections stay readable.
imports.kr
1from "./palette.kr" as palette2entry palette.Material34Material localPreview {5    noise = NoiseTexture(scale = 4)6    tinted = palette.WarmTint(color << noise.color)7    bsdf = PrincipledBSDF()8    out = MaterialOutput()9    tinted.output >> bsdf.base_color10    bsdf.bsdf >> out.surface11}

Add comments and doc comments

Comments are ignored by the graph, while doc comments can describe groups or nodes for editor tooling.

  • `//` starts a regular line comment.
  • `#` starts a regular line comment. Provide it not after `=` where it be comes used for hex color
  • `/* ... */` writes a block comment.
  • `##` and `/** ... */` are doc comments.
  • Use comments for intent, not for repeating the node type already shown in the source.
comments.kr
1## Shared material tint used by several files.2Material tint {3    // Incoming color is multiplied with a warm accent.4    mix = MixRGB(5      blend_type = MULTIPLY6      color1 << color 7    )8    mix.color >> output9}1011/*12This entry can stay small because tint does the reusable work.13*/14Material {15    noise = NoiseTexture(scale = 5)16    tinted = tint(color << noise.color)17    bsdf = PrincipledBSDF()18    out = MaterialOutput()19    tinted.output >> bsdf.base_color20    bsdf.bsdf >> out.surface21}