makeacut

makeacut is a small, human-readable language for precise two-dimensional fabrication drawings. A .mac file describes geometry in physical units and labels it as cutting, scoring, engraving, or guide work. makeacut can render the result as SVG or PNG, and includes a browser editor with a live preview.

This guide is the manual for the current implementation.

units mm
coordinates cartesian

wall_width = 120
wall_height = 50
door_width = 20

cut wall:
    rect wall_width wall_height

cut door:
    zero
    move (wall_width - door_width) / 2 0
    rect door_width 30

score roof_line:
    zero
    move 0 40
    line wall_width 0

Using the browser editor

The editor can render live or when Run is pressed, zoom the preview, show or hide numbered coordinate rulers while retaining the background grid, and save or open makeacut designs. The preview's Output menu can print the current design or download it as PNG or SVG. With the 3D feature enabled, 3D Printer opens an export dialog with a 3D preview, dimensions and part thicknesses. STEP is the default for further CAD editing; binary STL is available for slicers, with fine, standard and draft detail settings. Import STL in millimetres. Closed cut outlines become solids using the design's thickness defaults and cut-block overrides (5 mm when undeclared), preserving holes and individual part heights. Paper tabs use their straight baselines; scores, engraving, guides and labels do not add material. All parts are saved in one file in their drawing positions. Arrange them and prepare printing in your CAD application or slicer.

The white material shading fills overlapping closed cut outlines without cancelling their overlap. A fully enclosed outline forms a hole, with further nested outlines forming islands. Outlines sharing a boundary segment stay filled, even when one fits inside the other. Separate intersecting outlines remain separate pieces in 3D. Use add: to merge their actual cut geometry or subtract: to remove one shape from another; preview shading does not change the cut lines.

Live preview turns off and its control is disabled when you reach your plan's autorun line cutoff (500 by default). Use Run for larger designs. At your plan's maximum line cutoff, Run is also disabled (1,000 lines on Free). A message at the bottom explains the limit. Comments and blank lines count, including the final empty line after a trailing newline, just as in the line numbers. You can keep editing and autosaving; delete lines below the cutoff to re-enable the controls, then select Live preview again if you want autorun.

Choosing Print opens the print planner. A named setup records its output mode, physical page or bed size, units, margins, overlap, and output options. The hosted editor saves setups to the user's account; the standalone editor saves them in the current browser. Paper presets include common ISO and North American sizes; laser presets include several common bed sizes. Custom widths and heights can be entered in millimetres or inches.

The planner divides a design into physical pages and shows the entire job as a two-dimensional page layout before opening the system print dialog. It can add alignment guides, turn glue tabs into their underlying straight lines, turn scores into ordinary lines, omit labels or cuts, add page numbers and a physical-size ruler, and prepend a one-page assembly plan. Omitting cuts is useful for a separate engraving pass on a laser cutter. Use 100% scale and disable any additional margins in the system print dialog so the printed dimensions match the plan and ruler.

The local editor saves .mac files in its configured design directory. The hosted editor stores designs in flat catalogs. Its Open dialog searches design names, descriptions, and catalog names across the user's library and the available system catalogs.

Each hosted user has a read-only Recent Designs catalog containing the ten unique designs they opened most recently. Opening a design again moves it to the top. The history and last-opened catalog are stored server-side, and shared designs remain in the list only while the user can still view them.

Hosted designs have stable URLs that can be copied and shared. The Save dialog controls visibility:

Only the owner can update a design. A signed-in viewer of a hidden or public design opens it read-only and can use Clone to create an independent private design in one of their own catalogs. Anonymous viewers receive a read-only editor and must sign in to clone. Clicking a System Catalog preview also opens the saved design read-only. Use Clone to choose a name and one of your catalogs, then create an editable personal design.

While you edit a hosted design that you own, makeacut automatically saves a private working draft after a short delay. During continuous typing it saves at least every five seconds. A small browser-local backup protects the newest keystrokes if a request or page close interrupts an autosave. Shared and public viewers continue to see the last checkpoint, never the owner's working draft.

Save first flushes the working draft and then creates an immutable checkpoint revision, including its preview and metadata. Revision and draft checks prevent an older browser tab from silently overwriting newer work; reload if the editor reports that autosave stopped because another tab changed the draft. Personal catalogs cannot be nested, and a nonempty catalog must be emptied before it can be deleted.

Deleting a design first moves it to Deleted Designs. From that catalog, choose Restore to return it to its previous catalog, or Delete Forever to remove it permanently. If the previous catalog no longer exists, Restore places the design in My Designs.

For a saved design you own, open Save and choose History to browse its revisions. When the autosaved source differs from the last checkpoint, the history begins with Working draft. Saved revisions show their metadata, source, and captured preview. Browsing history is read-only and does not change the source currently open in the editor. Choose Make Current to copy a selected older snapshot into a new revision without deleting any intervening history. The design remains in its current catalog.

Editing geometry in the preview

Place the source cursor on a line, score, bezier, rect, triangle, circle, or arc command, a directional command (right, left, up, down), or a cursor command (move, zero, angle, turn), then enable Edit geometry above the preview. The selected shape or cursor control is highlighted, with handles for its dimensions, endpoint, or Bézier controls. Its starting position remains fixed. The inspector below the preview also lets you enter exact values in the command's units.

Two-value endpoints and explicit controls move freely in the command's coordinate plane. A one-value endpoint moves along the starting heading; --tangent adjusts the control distance along that heading and stays positive. Rotated and reflected planes, units, and scale are accounted for automatically.

Rectangles have width and height handles. Adding --radius gives a rectangle a corner-radius handle, constrained between zero and half the shorter absolute dimension. Triangles have base and height handles; --apex adds an apex-offset handle, and --rounded adds a radius handle constrained to the incircle radius. Circles have a radius handle; the three-value circle radius start sweep form also has start and sweep angle handles. Arcs have a radius handle at their center and a sweep handle at their endpoint. Dragging a sweep preserves its positive or negative direction; enter a signed value in the inspector to reverse it. Angles are measured in degrees.

Directional commands have a length handle constrained to their axis. move and zero have a position handle and x/y inputs; move --zero and zero use the current origin. Dragging a bare zero adds its two coordinates. Named references (move --to and zero --at) remain editable in the source. angle and turn have a rotation handle: angle sets a direction relative to the coordinate plane, while turn rotates from the incoming heading. The faint reference ray shows the direction from which the angle is measured. Rotation inputs are in degrees and retain signed values and complete turns.

The inspector's Round menu controls precision while dragging: None keeps the current precision, 0 rounds to whole numbers, 0.1 to tenths, and 0.01 to hundredths in the command's units. Previewed and saved values use the same rounding. Selecting a command initially matches rounding to its numeric arguments: whole numbers select 0, tenths 0.1, hundredths 0.01, and finer values None. You can override this for that command. Choosing a precision immediately rounds the selected command's numeric arguments as one undoable edit, then applies to subsequent drags. Tangent lengths stay positive, with a minimum of one rounding step. Values entered directly in the inspector keep their exact precision.

Dragging previews the whole design, including geometry that depends on the edited command. The white area grows or shrinks to fit the geometry and handles, including points dragged outside its previous boundary. Resizing keeps document coordinates in the same screen position while dragging. Release to change its numeric arguments in the source, or press Escape to cancel. Each completed drag is one Undo step. Options, comments, indentation, and line continuations are preserved. Expressions and lengths inherited from a walk remain protected; edit those in the source.

If a command appears in several part placements or repeats, all instances are highlighted. Choose a reference instance in the inspector or click its highlighted segment. Changing the command updates every instance. For add and subtract, handles follow the original command before the boolean operation.

This first version supports lines and Bézier curves. The toggle is unavailable on read-only designs. Handles and the inspector are excluded from printing and exports.

Editor shortcuts

Document defaults

Unless declared otherwise, a document starts with:

Top-level geometry is allowed and is treated as an unnamed cut operation. Using explicit fabrication blocks is recommended for real designs.

Declarations

The units, coordinates, scale, and defaults declarations must appear before geometry. They cannot be changed inside a part.

General defaults

Set the thickness and material color with a document-wide block:

units mm
stock = 2.5

defaults:
    thickness stock * 2
    color "#1d5c43"

cut panel:
    rect 40 20

thickness accepts a positive numeric expression using variables defined above the block. Explicit thickness uses the document's units and modeling scale. Without a declaration, thickness remains 5 physical millimetres. Thickness does not change 2D geometry, printing, or SVG/PNG exports.

color sets the 3D rendered material color. It accepts a quoted six-digit hex color, such as color "#3366ff"; quotes keep the # from starting a comment. Omitting it uses green (#1d5c43). 3D lighting shades the chosen color. The 2D preview and SVG/PNG exports keep their existing fills and operation styles.

A general defaults block supports thickness and color, together or separately. Declare it before geometry, at the document's top level; defaults inside parts are not supported. Expressions are evaluated when the block is read. Reassigning a variable later does not change a previously evaluated default. Multiple blocks before geometry are allowed, with later settings replacing the earlier values for the same key.

defaults tab: continues to configure paper tabs separately.

Override the thickness on a cut block when a piece uses different stock:

cut base --thickness 3:
    rect 40 20

move 60 0
cut panel --thickness default:
    rect 40 20

--thickness accepts a positive numeric expression or the keyword default, which selects the document's thickness. Omitting the option also uses that default. Set thickness on the outline that owns the material: holes inherit their surrounding piece's thickness. A hole-only block cannot override thickness. Without a Boolean result override, subtraction tools cannot set thickness, and joined outlines in an add: block must have the same thickness; disconnected pieces can have different heights. add --thickness expression: and subtract --thickness expression: explicitly set the resulting material's thickness, overriding child heights. Both also accept default. Primitive commands such as rect do not accept --thickness.

The default keyword can also be a part parameter's default value or a placement argument, and can be forwarded through nested parts:

part panel:
    parameters:
        thickness default
    cut --thickness thickness:
        rect 40 20

place panel --thickness 3
move 60 0
place panel --thickness default

The keyword is unquoted; "default" is text, not a thickness value. Numeric expressions are evaluated when the cut block is read.

Units

units mm

The supported units are mm, cm, m, in, and ft. Choose one per document. All authored coordinates, dimensions, computed geometry results, and debug coordinates use that unit. Rendered physical output is converted to millimetres.

Coordinate system

coordinates page

In page coordinates, positive x points right and positive y points down.

coordinates cartesian

In Cartesian coordinates, positive x points right and positive y points up. move dx dy and line dx dy follow the selected coordinate system. The directional commands up and down always describe visual directions.

Modeling scale

units m
scale 1:160

scale numerator:denominator converts prototype dimensions to model output dimensions. With the declarations above, an authored 16 m length renders as 100 mm. Both sides may be numeric expressions and must be greater than zero. Computed results and debug points are still reported in authored units.

Cursor and geometry

makeacut uses one persistent cursor with a position and an angle. Geometry commands are relative to its current position; there is no absolute-coordinate mode. The cursor survives fabrication operation boundaries.

Every cursor and geometry command accepts an optional --name identifier label. Names must be identifiers. Named drawing commands expose numbered points for move --to. Straight line, score, directional commands, close, and tab can also define a local coordinate plane with from or zero --at. A tab uses its logical baseline. Curves and closed shapes expose point references but cannot define a straight coordinate plane. Names on cursor commands are labels and do not expose drawing points:

move 10 5 --name panel_start
line 20 --name bottom_edge
tab 15 --depth 5 --name glue_edge
close --name outline

Command options use --: for example, --name, --score, --zero, --depth, --taper, --left, --right, --noscore, and --off. Existing undashed statements are still accepted. When a statement uses a -- option, use that spelling for all its options. This keeps option words available as expression variables, as in tab 20 --depth depth. Block declarations and settings, such as defaults tab: followed by depth 5, retain their statement syntax.

from named_line:

Use the start and direction of an earlier named line as a temporary local coordinate plane:

coordinates cartesian

line 10 10 --name line_1
from line_1:
    line 0 10

The named line's start becomes local (0, 0), and its direction becomes the positive x-axis. The new plane is reflected across that x-axis: its y-axis and positive turn direction point to the opposite side of the named line from the plane that created it. This means the same walk body can reuse the shared edge and unfold a new face on its other side.

All relative geometry in the indented block—including moves, directional commands, rectangles, arcs, placed parts, and repeats—uses the reflected plane. zero returns to the local origin, and angle 0 points along the named line.

The named line must appear before the from block and have non-zero length. When the block ends, the previous cursor position, angle, and coordinate plane are restored. Each named line remembers the plane that created it, so a later from continues unfolding correctly even after that plane's block has ended. from blocks may also be nested.

zero --at named_line

Use a named line as the new coordinate plane without opening a block:

line 20 --name shared_edge
zero --at shared_edge
line 0 10

This selects the same origin, direction, and reflected side as from shared_edge:, but the plane remains active for the statements that follow. A later plain zero returns to the start of shared_edge; another zero --at selects a different named line. Inside a restoring block such as group or from, the previous plane is restored when that block ends.

The cursor angle is the current drawing direction, normalized to the range from 0° through just under 360°. A non-zero coordinate-relative line sets it to that segment's direction, while an arc or Bézier leaves it at the ending tangent. An angle of 0° points right. In page coordinates, 90° points down; in Cartesian coordinates, 90° points up. move, rect, triangle, and circle preserve the angle. turn changes the angle relatively, while angle sets it within the current coordinate plane. The cursor angle supplies the starting tangent for arc and the direction for a one-value line; changing the cursor angle does not itself rotate other movement or shape commands.

To address a position from the document origin, reset and move:

zero
move 50 20

Every command argument can be a numeric expression.

Named drawing points

Add --name to a drawing command, then use move --to name.p1, name.p2, and so on to reach its saved points. Numbering follows the command's logical geometry, independently of how the outline is rendered or flattened:

Drawing command Numbered points Other point references
line, score, right, left, up, down p1: start; p2: end start = p1; end = p2
triangle p1: base start at the cursor; p2: base end; p3: apex —
rect p1: cursor corner; p2: width corner; p3: opposite corner; p4: height corner —
Quadratic bezier p1: start; p2: control; p3: end start = p1; end = p3
Cubic bezier p1: start; p2, p3: controls in order; p4: end start = p1; end = p4
arc p1: start; p2: end start, end, center
Partial circle p1: start; p2: end start, end, center
Complete circle p1: positive x; p2: positive y; p3: negative x; p4: negative y center
tab p1, p2: baseline start and end; p3, p4: outer corners near the start and end start = p1; end = p2
close p1: cursor before closing; p2: original path start start = p1; end = p2
coordinates cartesian
triangle 40 30 --apex 0 --name face
move --to face.p3
circle 2

Here the circle is centered at the triangle's apex. move --to moves without drawing and preserves the cursor heading and current coordinate plane.

Rounded triangles and rectangles retain the original sharp vertices as their numbered points, even when a corner is no longer on the rounded outline. Negative dimensions preserve the same authored order. Bézier numbering includes a control generated by --tangent; quadratic and cubic end aliases always refer to their final endpoint. A tab drawn with --off or with tabs disabled behaves as a line and exposes only p1, p2, start, and end.

Circle cardinal points follow the current coordinate plane, including its rotation and reflection. Positive y points up in Cartesian coordinates and down in page coordinates. A complete circle always has four cardinal points, including the three-argument form with a sweep of 360 or -360; its authored start angle does not change the numbering. A full-turn arc still exposes its segment endpoints, which coincide, and its center.

References are saved when the drawing command executes and follow units, modeling scale, and part placement. They keep their positions after leaving a from or restoring block and after boolean operations. Reusing a name replaces its earlier point references. Placed parts export their named drawings to the caller; later placements of the same named drawing replace earlier ones. Sequential repeats retain the latest execution's points. Array repeats use the prototype's points, at the first copy's position, as existing named-line references do.

The name must already exist and the selector must belong to that drawing. Errors list the available points. Closed triangles, rectangles, and complete circles use numbered points without start or end aliases.

The named point samples demonstrate each drawing type, including control points and rounded corners.

move dx dy or move --to name.point

Move the cursor without drawing or changing its angle. A later line starts a new path there.

move 10 5

To move directly to a saved point of an existing named drawing, use --to:

move --to B1_1.start
move --to B1_1.end

This changes only the cursor position. It draws nothing and preserves the cursor angle, coordinate plane, and orientation. The named drawing must already exist. Numbered selectors such as .p1 and .p3, and .center for circular geometry, work the same way. --to is mutually exclusive with positional coordinates and requires a point selector.

Add --zero to reset the cursor before applying the move:

move 10 5 --zero

This is equivalent to zero 10 5, or to zero followed by move 10 5. It resets both position and angle, then moves from the current document or part origin without changing the reset angle. The option may also precede the coordinates, as in move --zero 10 5.

line distance or line dx dy

With one value, draw forward by that distance in the cursor's current angular direction:

line 20
arc 10 90
line 30

A positive distance preserves the angle. A negative distance draws backward and reverses the cursor angle by 180°, because the angle always describes the direction of the last non-zero line. A zero distance leaves position and angle unchanged.

With two values, draw by the given coordinate offset. This form sets the cursor angle to the segment's direction:

line 20 0
line 3.5 4.2
line 0 -6

Diagonal movement needs no separate command: give both x and y an offset. Both forms move the cursor to the segment endpoint. If no path is active, the path begins at the current cursor.

Use score with the same one- or two-value forms to emit a score rather than a cut:

cut outline:
    line 40 0
    score 0 20 --name fold
    line -40 0
    close

The scored segment remains part of the logical path: it moves the cursor, updates the angle and result.*, and is included in result.path_length. Later cut segments continue from its endpoint, and close still returns to the beginning of the complete path. The option form line 0 20 --score produces the same scoring behavior. The older line 0 20 score spelling remains accepted for existing statements.

score distance or score dx dy

Draw a score line using the cursor and angle behavior of line. With one value, it draws that distance in the current direction. With two values, it draws by the given coordinate offset and updates the cursor angle.

The command is distinct from a score operation block: score 20 draws one segment, while score fold: begins a named block of score geometry.

right distance, left distance, up distance, down distance

Draw in a visual direction and move the cursor. These commands are convenient for orthogonal paths.

right 40
down 20
left 40
up 20

right and left affect x. up always moves visually upward and down always moves visually downward, in both coordinate systems.

turn sweep_angle

Add an angle to the cursor's current direction without moving or drawing:

line 20
turn 90
line 10

Positive values increase the angle and negative values decrease it. The result is normalized to the range from 0° through just under 360°. turn does not interrupt an active path or replace result.*.

angle absolute_angle

Set the cursor to an absolute direction without moving or drawing:

angle 45
line 20

The supplied angle is normalized to the range from 0° through just under 360°. angle does not interrupt an active path or replace result.*.

label "text"

Add a centered text annotation to the nearest enclosing scope:

group row_1:
    label "Row 1"

    cut face:
        label "1"
        line E
        turn 120
        score E
        turn 120
        tab E

A label inside a fabrication operation is centered on that operation. A label directly inside a group is centered across all geometry created by the group. At the top level, it is centered on the complete drawing. Labels may appear before the geometry because their positions are resolved when their scope ends.

Closed faces use their area centroid, which gives triangles and other asymmetric faces a visually balanced label position. Open geometry falls back to the center of its logical bounding box. Tab protrusions are ignored, so a face label remains centered on the face rather than the complete tab outline. Labels use a fixed physical height of 4 mm and move with placed parts and repeated geometry. They appear in the preview, print output, PNG, and SVG, but are annotations rather than cut or score paths.

Label text must use double quotes. JSON-style escapes are accepted, and # inside the quoted text is not treated as a comment.

add [--thickness expression|default]:

Merge the closed cut outlines of all child shapes. The outer boundary of the combined material remains a cut; child edges inside that material become scores. Shared interior edges are emitted only once, including partial overlaps. Identical exterior outlines remain a single cut rather than becoming scores.

add:
    rect 20 15
    move 20 0
    rect 20 15

This produces a 40 by 15 rectangle with a score along the shared 15-unit edge. Overlapping shapes are split at their intersections: exposed edge portions stay cuts and covered portions become scores. A shape contained entirely inside another contributes an interior score outline. Disconnected shapes remain separate islands. Circles and circular arcs retain their native curves in fabrication output.

Use --thickness to choose the thickness of every resulting solid, including disconnected pieces. It accepts a positive expression in the document's units and modeling scale, or default for the document's thickness. The value is evaluated when the header is read. An explicit override resolves differing child thicknesses, including nested Boolean blocks and placed parts:

stock = 3
add --thickness stock * 2:
    cut --thickness 3:
        rect 40 20
    move 20 0
    cut --thickness 8:
        rect 40 20

This produces one solid with a thickness of 6, without changing the 2D geometry. Without the option, joined child outlines must have the same thickness and disconnected pieces retain their individual heights. An enclosing Boolean block's explicit override determines the final result's thickness.

The block supports ordinary drawing commands, cut operations, placed parts, repeats, and nested groups or add: blocks. It restores the cursor position, angle, and coordinate plane on exit, just like group:. It can also appear inside a fabrication operation or a walk: block. Geometry outside the block is not merged. Explicit scores, engravings, guides, labels, and debug points are retained.

At least one closed cut outline is required. Open cut lines are split at the material boundary: portions inside become scores and portions outside remain cuts. add: is not supported inside measure:. Dimensions obey the document's units, scale, and coordinate system. Calculations use a small numerical tolerance for shared endpoints and intersections.

Unlike group scored:, which recognizes duplicate complete line segments, add: merges actual filled shapes and handles intersecting boundaries. Scored groups inside an add: block participate in the enclosing merge.

subtract [--thickness expression|default]:

Remove every subsequent closed cut shape from the first one, in drawing order. A cutter fully inside the first shape creates a hole; a cutter crossing its edge creates a notch. Cutters outside the first shape have no effect. Overlapping cutters remove their combined area, and identical shapes leave no material.

subtract:
    rect 40 30
    move 10 10
    rect 20 10

This produces a 40 by 30 rectangle with a 20 by 10 rectangular hole. All resulting boundaries are cuts. Subtraction does not automatically create scores.

--thickness sets the thickness of all remaining material. It accepts the same expressions and default value as add: and cut:. With an explicit result override, base and cutter thicknesses are ignored; cutters only determine the removed geometry. Without the option, the base retains its thickness and cutters cannot set thickness.

subtract --thickness 6:
    rect 80 40
    move 20 20
    circle 6
    move 40 0
    circle 6

This produces a plate with two through holes and a thickness of 6. Use subtract --thickness default: to select the document's thickness explicitly.

Each ordinary closed outline is a shape, including outlines drawn with line, arc, and close. A nested add: or subtract: result counts as one shape, including any islands and holes. To use several shapes together as the base, combine them inside a first child add: block:

subtract:
    add:
        rect 20 20
        move 20 0
        rect 20 20
    move 15 5
    rect 10 10

Circles and circular arcs remain native curves. Explicit score, engraving, and guide geometry authored inside the block is clipped to the remaining material; operation names and kinds are retained. Labels and debug markers remain authoring annotations. Geometry outside the block is unchanged.

Like add:, subtract: restores the cursor position, angle, and coordinate plane on exit. It supports parts, repeats, nested groups, and use inside fabrication operations and walk:. Units, scale, and coordinate systems apply normally. Children must have closed cut outlines; open lines cannot define subtraction areas. A single child retains its shape. An empty block is an error, and subtract: is not supported inside measure:. A fully removed result contains no drawable geometry.

group [label] [scored]:

Run a block of drawing commands, then restore the position and angle the cursor had on entry. An optional identifier can label the group for clarity:

move 20 10

group detail:
    rect 15 8
    move 4 2
    circle 1

line 30

The rectangle and circle remain in the output, while line 30 begins from (20, 10) with the original cursor angle. The label does not change rendering or cursor behavior. Debug points and result.* values produced inside the group also remain. Ending a group ends its active path, so drawing after the group starts a new path at the restored cursor. Groups can be nested and can be used inside fabrication operations, parts, and measure: blocks.

Add the scored modifier to derive shared score lines from cut geometry. It can follow an optional group label, or be used by itself:

group row_A scored:
    zero 0 10
    cut left_face:
        walk --turn -90 --stride 10:
            tab
            line
            tab
            tab

    zero 10 10
    cut right_face:
        walk --turn -90 --stride 10:
            line
            line
            line
            line

Within one group scored: scope, two or more cut-line segments with the same endpoints are treated as one interior edge. Direction does not matter and a small coordinate tolerance accommodates trigonometric rounding. All copies of the shared edge are removed from the cut paths and one score segment is emitted in their place. Cut lines contributed by placed parts participate too, including parts placed inside nested groups and walks. Unique lines remain cuts. Tab outlines remain cuts, and explicit score commands and tab score settings continue to work normally. Separate group scored: scopes do not interact.

A group may also declare one or more experimental browser backgrounds:

coordinates cartesian

group artwork:
    background "placeholder":
        at 0 0
        clip
        tile

    cut panel:
        rect 80 50

The quoted value is a logical image name. During this experiment every name resolves to the bundled background-1.jpg. The image box initially has the group's logical width and height and uses a centered cover-style fit. at x y positions its anchor in the group's coordinate plane using authored units. In page coordinates that anchor is the image's top-left corner; in Cartesian coordinates it is the bottom-left corner, so positive y still points visually upward. The defaults are at 0 0 and clip shape.

Bare clip is shorthand for clip shape. It clips to the union of the closed logical cut outlines in the group, so adjacent faces remain one continuous region and score lines do not create gaps. Tab protrusions are excluded. clip bounds retains rectangular logical-bounds clipping. clip none and noclip are equivalent and allow the image box to extend outside the group, expanding the browser preview as needed.

tile repeats the current image box horizontally and vertically as many times as needed to cover the group. With either clipping mode, partial edge tiles are cropped; without clipping, complete edge tiles remain visible. Without tile, one image is drawn. Backgrounds render beneath fabrication geometry. They currently appear only in hosted browser previews, are omitted from PNG and SVG exports, and are not yet supported inside reusable parts.

pattern kind:

Patterns are repeating vector surface markings attached to the closed cut shapes in their containing block. They always tile and remain editable as a pattern; there is no image asset and no tile setting.

units mm

defaults pattern:
    color "#aaaaaa"
    line-width 0.15 mm
    groove-width 0.4 mm
    depth 0.3 mm

group wall:
    subtract:
        rect 80 50
        move 30 15
        rect 20 25

    pattern bricks:
        size 8 3
        stagger 0.5
        at 0 0
        angle 0
        margin 1

Declare a pattern directly inside a part, cut:, group:, add:, or subtract: block, with one or more indented settings. The entire block's closed cut geometry is its target, regardless of whether the pattern appears before or after the geometry. A pattern does not move the drawing cursor, affect measurements, or change the cut outline. It clips to the finished material, including holes, curved outlines, and later boolean subtractions. Paper glue tabs are excluded using their baselines.

Pattern Layout setting Built-in size in authored units
lines spacing distance or size width height (height is the spacing) 5
crosshatch spacing distance or size width height 5 × 5
tiles size width height 5 × 5
bricks size width height, optional stagger fraction 8 × 3
floorboards size length width, optional stagger fraction 20 × 3

Bricks shift alternate courses; floorboards advance their end joints by the stagger fraction on each successive row. The default stagger is 0.5; values must be at least zero and less than one. Size and spacing must be positive, and cannot both be declared. Dimensions and settings accept expressions. Changing the shape size reveals more repeats without stretching the pattern.

at x y offsets the repeat origin from the containing block's entry cursor, in its local coordinate plane. It defaults to at 0 0. angle degrees rotates the pattern in that plane. Both page and Cartesian coordinates are supported. An origin and angle shared by adjacent groups can align their joints. A single group covering several adjacent faces has one continuous pattern across those faces. In a part, the origin is the part's local (0, 0); placing the part moves the pattern and geometry together. In a cut, the origin is the cursor at the cut header. Moves inside either block do not change the pattern origin.

Reusable parts do not need a wrapper group around their patterns:

part pillar:
    pattern bricks:
        size 10 1.25
        at 0 0
    cut --thickness 2:
        rect 1.5 11

place pillar
move 19.5 0
place pillar

A pattern inside a cut: applies only to that cut. Patterns in enclosing parts or groups also apply, so nested patterns can be combined. Score, guide, and engraving operations do not provide material surfaces for patterns.

margin distance leaves a clear strip around outside edges and holes, without introducing gaps at internal seams. The margin is measured to the widest of the paper stroke and the groove footprint. It defaults to zero. Curved margin boundaries use conservative sampling with up to 0.01 mm tolerance; ordinary pattern clipping retains native curves.

The following appearance settings can be set per pattern or inherited from a defaults pattern: block declared before geometry:

Setting Built-in physical default Output
color "#rrggbb" "#aaaaaa" Paper, SVG/PNG, and 2D preview
line-width distance 0.15 mm Paper and vector engraving display
groove-width distance 0.4 mm 3D groove width
depth distance 0.3 mm 3D groove depth
margin distance 0 Clear strip around edges and holes

Unqualified distances follow units and scale. An explicit suffix such as depth 0.3 mm or groove-width 0.04 cm fixes that distance in physical output units, independent of modeling scale. This lets prototype brick dimensions scale down while grooves remain manufacturable. at, size, and spacing always use authored units; angles and stagger fractions are dimensionless.

Patterns work inside reusable parts and retain their placement transforms. Placed patterned parts can be used in flow right: or repeat count every spacing: blocks. A sequential repeat count: can repeat a group containing its own pattern. Cut operations with patterns can also be used directly in spaced repeats. Multiple patterns on a block are allowed and are combined.

Paper output uses light lines. Laser mode renders the same paths as a separate engraving group; power, speed, and passes remain settings in the laser's software. Exported SVGs contain real clipped paths with engrave pattern classes and depth/width metadata, rather than SVG repeat fills or bitmap images. SVG and PNG downloads include patterns.

3D output subtracts rectangular grooves from the top face. Crossings do not accumulate depth. Each groove must be shallower than its target material; through-cutting depths produce a source diagnostic. Both STEP and STL export use the same solids. The 3D preview defaults to Surface lines, drawing the clipped pattern strokes on each part's top face for faster interaction. Select Grooves in the 3D Printer dialog to see the actual grooved solids. Surface lines show the pattern color and layout, with thin screen-space strokes rather than physical groove width or depth. STEP and STL exports always include the grooves, whichever preview mode is selected. The 2D and surface-line previews construct grooved solids only when a Grooves preview or export is requested. Existing handwritten engrave geometry retains its current behavior.

To bound geometry generation, patterns are limited to 5,000 vector segments per design and 1,000 groove segments per material part for grooved 3D output. Increase size or spacing when a density diagnostic appears; details are never silently omitted.

arc radius sweep_angle

Draw a circular arc beginning at the cursor and tangent to the cursor angle. The arc moves the cursor to its endpoint and adds the sweep to the cursor angle.

line 20 0       # cursor angle is 0°
arc 10 90       # radius 10; turn through +90°
line 20         # continues along the ending tangent

The radius may be zero or positive. A negative radius is invalid. The sweep is measured in degrees, must be non-zero, and cannot have an absolute value greater than 360. A positive sweep increases the cursor angle; a negative sweep decreases it. Therefore a positive sweep is visually clockwise in page coordinates and counterclockwise in Cartesian coordinates.

With a radius of zero, arc 0 sweep_angle is a pure turn: it is equivalent to turn sweep_angle, emits no geometry, preserves the cursor position and active path, and does not replace result.*. It retains the arc command's non-zero, at-most-360° sweep restriction.

Starting at 0°, arc 10 90 ends at offset (10, 10) in authored coordinates and leaves the cursor angle at 90°. arc 10 -90 ends at (10, -10) and leaves the angle at 270°.

A sweep of 360 or -360 draws a complete circle tangent to the cursor, returns to the same position, and returns to the same normalized angle. This differs from circle, whose centre is the cursor and which never moves it.

bezier distance|dx dy --control cx cy [cx cy]

Draw a quadratic Bézier with one control point, or a cubic Bézier with two. Endpoint arguments follow line: one value places the endpoint that distance along the current cursor angle; two values give an offset in the current coordinate plane. Every explicit control point is also an offset from the curve's starting cursor, including the second control of a cubic:

bezier 40 0 --control 20 30
bezier 60 0 --control 20 30 40 -30

Controls follow the selected coordinate system and any reflected from plane. All lengths support expressions, authored units, and modeling scale. Changing the cursor angle affects a one-value endpoint, but does not rotate explicit control offsets.

bezier distance|dx dy --tangent length [--control cx cy]

Use --tangent to place the first control point along the current cursor angle. Its positive length determines the first handle's reach. Without --control, this draws a quadratic. With one explicit second control, it draws a cubic:

line 20 0
bezier 40 20 --tangent 15 --control 25 20
line 15

The curve starts in the line's direction and leaves the cursor pointing along its ending tangent. A following one-value line or tangent-based Bézier continues in that direction. Use angle or turn beforehand to choose a different starting tangent. Matching tangents gives matching direction at the join, but does not guarantee matching curvature.

If the final control equals the endpoint, the ending direction uses the nearest distinct preceding control or start point. A curve with every point equal preserves the cursor angle. Curves can return to their start, forming loops; an endpoint offset of zero does not by itself mean zero curve length.

Both forms accept --score and --name identifier, in the same positions as line options. Scoring moves the cursor and contributes to the logical path length; later close still returns to the original path start. Named curves support move --to name.start|end and debug labels. from and zero --at require a straight baseline. Curves cannot be converted to tabs.

SVG export preserves native quadratic and cubic paths. PNG rendering samples curves to pixel accuracy. Curve lengths are calculated numerically; bounds use the curve extrema, excluding unused control-point overhangs. add and subtract preserve Bézier fragments at intersections with lines, circles, arcs, and other Béziers. Background clipping and label centroids use sampled curved outlines.

See the eight progressively more complex samples for complete designs.

close

Close the active path with a segment back to its first point. The path must already contain at least one drawn segment. After closing, the cursor is at the path's first point and, when the closing segment is non-zero, its angle is the direction of that segment.

line 20 0
line 0 10
line -20 0
close

zero

Reset the cursor to the document origin (0, 0) and its angle to 0° without drawing.

zero

Supply coordinates to move immediately after resetting:

zero 10 5

This is equivalent to move 10 5 --zero. Inside a part, both forms use that part's local origin. Coordinates follow the selected page or Cartesian coordinate system.

rect width height [--radius value]

Draw a rectangle at the cursor. The cursor position and heading stay unchanged.

move 10 20
rect 30 40

In page coordinates, positive width extends right and positive height extends down. In Cartesian coordinates, positive height extends up. Negative width or height is allowed and extends in the opposite direction. Neither dimension may be zero.

move 7.45 4.2
rect -0.55 -4

Add --radius to round all four corners inward. Width and height describe the full outer dimensions; the cursor anchors the same bounding-box corner as an ordinary rectangle, even though that corner is no longer on the outline. The rectangle follows the current coordinate plane, independently of the cursor heading.

rect 80 50 --radius 6
rect WIDTH HEIGHT --radius CORNER_RADIUS
rect -40 25 --radius 4

The radius takes a numeric expression in the current units and follows the modeling scale. It must be zero or positive and cannot exceed min(abs(width) abs(height)) / 2. Invalid radii produce an error rather than being silently reduced. Omitting --radius or setting it to zero draws an ordinary rectangle. Options such as --radius and --name may appear in any order.

A radius equal to half the shorter absolute dimension produces a capsule; when both dimensions have the same magnitude, it produces a circle within the rectangle's bounds:

cut slot:
    rect 30 10 --radius 5

Rounded rectangles are closed outlines with straight edges and tangent quarter-circle corners. SVG and fabrication output retain native circular arcs, including through add and subtract. Like other shapes, they do not replace result.*.

triangle base height [--apex offset] [--rounded radius]

Draw a closed triangle at the cursor, with its base along the x axis of the current coordinate plane. The base runs from the cursor to base; the third vertex is at x offset base / 2 and y offset height. Use --apex to set a different x offset, measured from the cursor. The offset may lie outside the base, and an offset of zero or base produces a right triangle.

triangle 40 30             # isosceles: apex at (20, 30)

For a right triangle, set --apex 0. The right angle is at the cursor, and base and height are the lengths of the two perpendicular legs. Set the apex offset equal to the base to put the right angle at the other end:

triangle 40 30 --apex 0
move 55 0
triangle 40 30 --apex 40

Choose another offset to draw a scalene triangle:

triangle 50 30 --apex 12

Positive height extends down in page coordinates and up in Cartesian coordinates. Base and height may be negative, but neither may be zero. The triangle follows the current coordinate plane independently of the cursor heading, and preserves the cursor position, heading, and result.* values.

coordinates cartesian
triangle 40 -30 --apex 0   # extends downward from the base

Add --rounded radius to round all three corners inward with tangent circular arcs. The dimensions and apex describe the original sharp triangle. Rounding removes its tips, so the rounded outline may have smaller bounds. The radius must be zero or positive and cannot exceed the triangle's incircle radius: abs(base * height) / perimeter, where perimeter is the sum of its three side lengths. Larger radii produce an error rather than being silently reduced. At the maximum radius the outline becomes the incircle. Omitting --rounded or setting it to zero produces a sharp triangle.

triangle 40 30 --apex 0 --rounded 3

For this right triangle, the hypotenuse is 50, so the maximum radius is 40 * 30 / (40 + 30 + 50), or 10 units. Setting --rounded 10 produces its incircle.

All values accept numeric expressions in the current units and follow the modeling scale. --apex, --rounded, and --name may appear in any order. SVG and fabrication output retain native circular arcs, including through add and subtract.

The triangle samples cover centered and off-center apexes, right angles at either end of the base, and rounded corners compared with a sharp guide outline.

circle radius [start_angle sweep_angle]

Draw a circle centered on the cursor. The radius must be greater than zero, and the cursor does not move.

move 25 25
circle 10

Add a start angle and sweep angle to draw only part of the circle:

circle 10 0 90      # quarter circle
circle 10 0 180     # semicircle
circle 10 45 270    # three-quarter circle
circle 10 180 -90   # sweep in the opposite direction

Angles are measured in degrees, with 0 pointing right along positive x. A positive sweep follows the selected coordinate system: clockwise in page coordinates and counterclockwise in Cartesian coordinates. A negative sweep goes the other way. The sweep must be non-zero and its absolute value cannot exceed 360 degrees. A sweep of 360 or -360 produces a complete circle.

Use either one argument or all three; circle radius start_angle by itself is not valid. Partial circles remain centered shapes and do not move the cursor.

Fabrication operations

An operation block records fabrication intent. Its optional name must be an identifier: letters, digits, and underscores, beginning with a letter or underscore.

cut outline:
    rect 100 50

score fold_line:
    zero
    move 0 10
    line 100 0

engrave serial_number:
    zero
    move 5 5
    rect 20 8

guide center:
    zero
    move 50 0
    line 0 50

Inside a part, --id can take a string parameter and use its value as the operation name:

part triangle_up:
    parameters:
        label

    cut --id label:
        line 20

place triangle_up --label "A01"

This produces the same named operation as cut A01:. The resolved value must be text and must follow the normal identifier rules. A quoted literal is also accepted, such as cut --id "A01":, although the ordinary cut A01: spelling is shorter for a fixed name. --id works with cut, score, engrave, and guide blocks.

The four operation keywords are:

Geometry inside an operation block is indented with spaces. Tabs are not accepted for language indentation.

A named cut operation automatically names every descendant line, score, tab, and bezier in encounter order, regardless of whether it is direct or nested in a group, from, sequential repeat, or walk block:

cut T2:
    score E  # name T2_1
    group:
        score E  # name T2_2
    line E  # name T2_3

Generated straight-segment names can be used by later from T2_1: blocks; generated curve names support endpoint moves and debug labels. Explicit --name identifier options are preserved instead of being replaced, while the following automatic name retains its encounter-order number. Other commands and debug statements do not consume a number.

When an operation belongs to a placed part, its implicit and explicit segment names are exported into the enclosing document as soon as the place statement runs. Their coordinates and orientation follow the placement, so a later from B1_1: or zero --at B1_1 works across groups, walks, nested parts, and reflected coordinate planes.

Selecting tabs by segment number

Declare tabs before the first segment in a cut operation to convert selected line segments into tabs:

cut A4:
    tabs 2 3

    turn 60
    walk --turn 120 --stride E:
        line  # A4_1 remains a line
        line  # A4_2 becomes a tab
        line  # A4_3 becomes a tab

The numbers use the same descendant encounter order as implicit edge names, including segments inside walk, group, from, and sequential repeat blocks. Labels, debug points, turns, and other non-segment commands do not consume a number. Converted tabs use the active defaults tab: settings and retain their implicit or explicit segment names.

Use an explicit empty selection when no lines should be converted:

cut A4:
    tabs none
    # ...

tabs none does not change explicitly authored tab commands. Omitting the declaration currently has the same rendering behavior. A cut operation may contain only one tabs declaration, and none cannot be combined with segment numbers. Selecting an existing tab is harmless; selecting a score or a segment number that does not exist is an error. Placement-level --notabs still converts all resulting tabs back into ordinary lines.

A part can expose the selection as a parameter. tabs is a contextual keyword, so it may also be used as the parameter name:

part triangle_up:
    parameters:
        label
        tabs none

    cut --id label:
        label label
        tabs tabs
        turn 60
        walk --turn 120 --stride E:
            line
            line
            line

place triangle_up --label "A4" --tabs 2 3

The default none supplies an empty selection. A placement may supply one or more unique positive segment numbers to --tabs; the operation validates the resolved selection using the same rules as a literal tabs declaration.

Variables and expressions

Assign a numeric expression with name = expression:

wall_width = 120
door_width = 20
margin = (wall_width - door_width) / 2

Variable names follow the same identifier rules as operation names. They are available after their assignment. The operators are +, -, *, and /; parentheses control grouping. Decimal and scientific-notation numbers are supported.

Shape command keywords cannot be used as variable, part, or part parameter names. This includes triangle, like rect and circle. The words ellipse, polygon, slot, star, sector, and ring are also reserved for future shape commands; they do not draw shapes yet. Longer identifiers such as triangular_face and ring_width remain valid. Operation names, --name labels, and quoted text may still use these words.

Whitespace separates command arguments, so parentheses are the clearest way to make a multi-token expression one argument:

move (wall_width - door_width) / 2 10
rect door_width 30

A backslash continues a logical statement on the next physical line:

opening_width = wall_width \
    - left_margin \
    - right_margin

Everything after # is a comment:

# Front elevation
rect 120 50  # outside edge

Numeric functions

Function arguments are normally separated by whitespace. Commas are accepted but optional.

Function Result
abs(value) Absolute value
sqrt(value) Square root of a non-negative value
sin(angle) Sine of an angle in degrees
cos(angle) Cosine of an angle in degrees
length(dx dy) Length of a vector from its x and y offsets
distance(x1 y1 x2 y2) Distance between two points
min(value ...) Smallest of one or more values
max(value ...) Largest of one or more values
round(value) Nearest integer, with halves away from zero
round(value places) Round to an integer number of decimal places

Examples:

SLOPE = length(SHORT_WIDTH / 2 ROOF_HEIGHT)
root = sqrt(81)
rise = sin(30) * length
run = cos(30) * length
gap = distance(0 0 3 4)
safe_width = max(10 requested_width)
display_width = round(safe_width 2)

Functions can be nested:

size = max(abs(-3) length(6 8) / 2)

Results from drawing

After line, arc, bezier, a directional command, or close, makeacut publishes these read-only values:

Value Meaning
result.length Length of the most recent segment
result.path_length Total length of the current path, including the closing segment after close
result.dx Signed x offset of the most recent segment
result.dy Signed y offset in the selected coordinate system
result.ax Endpoint x coordinate from the current origin
result.ay Endpoint y coordinate in the selected coordinate system

The values use authored units, even when modeling scale is active. Save a result in a regular variable before later drawing replaces it:

line 20 0
line 0 30
close

diagonal = result.length
perimeter = result.path_length

zero
move 50 0
rect diagonal diagonal

move, turn, angle, a zero-radius arc, zero, shapes, and debug do not replace the last segment result.

Tabs

Draw a glue tab along a baseline with the same one- or two-value forms as line:

tab 20
tab 20 5

The one-value form advances in the cursor's current direction. The two-value form uses dx dy and updates the cursor angle to that baseline direction. The cursor finishes where the equivalent line would finish, but the uncut baseline is replaced by the tab's tapered outer edge.

Less common behavior uses named options, which may appear in any order:

tab 20 --depth 8 --taper 3 --right --noscore
tab WIDTH --depth TAB_DEPTH --left --score

--depth and --taper take numeric expressions. --left and --right select the side relative to the direction of travel. --score adds the omitted baseline as a separate score line, while --noscore suppresses it. The opposing side and score options cannot be combined in one command.

Add --off to convert a tab into an ordinary line along its baseline. Tab geometry and its automatic score line are both suppressed; cursor movement, angle, and result.* values are exactly those of the equivalent line:

tab 20 --off
tab 0 WALL_HEIGHT --name glue_edge --off

Tabs currently default to a physical depth of 10 mm, a physical taper of 5 mm at each end, the left side, and an automatic score line. Explicit dimensions use the document's authored units and modeling scale. Taper must be non-negative and cannot exceed half the baseline length; exactly half produces a triangular tab.

Change any of those defaults for the entire document with a top-level block:

defaults tab:
    depth 5
    taper 2.5
    noscore

The settings are depth value, taper value, left, right, score, and noscore. A block may contain only the settings that need to change; omitted settings retain their current values. More than one block is allowed before geometry, with later blocks updating only the settings they contain. Numeric settings accept expressions and use the document's authored units and modeling scale, just as options written directly on a tab command do.

Options on an individual tab take precedence over document defaults:

defaults tab:
    depth 5
    taper 2.5
    noscore

tab 20                 # depth 5, taper 2.5, left, noscore
tab 20 --right           # depth 5, taper 2.5, right, noscore
tab 20 --score --depth 8   # depth 8, taper 2.5, left, score

Placed parts inherit the active document defaults. A defaults block cannot be declared inside a part.

For results, result.length, result.dx, and result.dy describe the tab's baseline. result.path_length includes the actual tapered cut outline.

Measuring without drawing

A measure: block evaluates path commands in private coordinates that start at (0, 0). It publishes result.* values but emits no geometry and restores the document cursor afterward.

SHORT_WIDTH = 24
ROOF_HEIGHT = 10

measure:
    line SHORT_WIDTH / 2 ROOF_HEIGHT

SLOPE = result.length

A measure block accepts move, line, tab, right, left, up, down, turn, angle, arc, bezier, close, and zero. It must draw at least one segment. Shapes, placed parts, and debug points are not allowed inside it.

Reusable parts

Define a part with a parameters: block. A name by itself is required; a value after the name is its default. Required parameters must come first:

part window:
    parameters:
        label
        width
        height 20

    cut opening:
        label label
        rect width height
    score center:
        move width / 2 0
        line 0 height

Each part has private coordinates beginning at (0, 0). Place it at the document cursor with named arguments. Arguments may be numeric expressions, quoted text, true, false, default, or the empty-selection value none where the receiving command supports it:

move 10 20
place window --label "Front" --width 30

Defaults are evaluated when the part is placed, in declaration order. A later default can therefore use an earlier numeric parameter. Named arguments may be given in any order. Missing required arguments, unknown names, and supplying a parameter more than once are errors.

Positional arguments remain supported and must precede named arguments:

place window "Front" 30
place window "Back" --width 40 --height 25

The original header form remains valid for required parameters:

part window width height:
    cut:
        rect width height

Placement translates the part's operations and debug points but does not change the caller's cursor position or angle. Parts can place other parts, may be used before their definition, and cannot recursively place themselves. A part must contain geometry when it is placed.

Use the notabs placement option to turn every tab produced by that placement into its equivalent line. This also applies to tabs in nested parts and removes their automatic score lines:

place short_side --notabs
place panel WIDTH HEIGHT --notabs

The legacy undashed notabs spelling is also accepted.

Flow layout

flow right: places parts consecutively using each part's logical horizontal extent, then advances the cursor by the total distance:

part short_side:
    cut:
        rect 10 8

part long_side:
    cut:
        rect 25 12

flow right:
    place short_side
    place long_side
    place short_side
    place long_side

Only place statements are allowed inside the block. Every placed part must extend to the right of its local origin. Geometry that overhangs to the left of that origin does not add a gap after the part. Tabs on either side are measured using their original baselines rather than their protruding cut outlines, so a right-facing tab does not push the following part to the right. Ordinary place leaves the cursor unchanged; the cursor advance is specific to flow right:.

Walks

walk applies a cursor step after every drawing command, placed part, or group in its body. Drawing commands and placements keep their own dimensions and options:

walk 0 0 TURN:
    score E
    debug first_edge
    line E
    line E
    line E
    tab E

The positional forms remain available:

walk distance [debug]:
walk dx dy [debug]:
walk dx dy angle [debug]:

The same behavior can be written with UNIX-style long options:

walk --step distance:
walk --move dx dy:
walk --move dx dy --turn angle:
walk --turn angle:

Positional values must appear before the first named option. After that, named options may appear in any order. Each option has a fixed number of values: --move takes two expressions; --step, --turn, and --stride each take one; and --debug takes none. --step and --move are alternatives and cannot both be used. A named option may follow positional values, so walk 0 0 --turn 90: is valid. Repeating an option or specifying the same setting both positionally and by name is an error. The older undashed named clauses remain accepted for compatibility with existing designs.

The optional --stride length option supplies the length for any line, score, tab, right, left, up, or down command in the body that has no positional value of its own:

cut:
    walk --turn 120 --stride E:
        score
        line
        line

An explicit child length overrides --stride, including a two-value vector. Options do not count as a length, so forms such as line --score, tab --left --noscore, and score --name fold receive the stride. --stride must be greater than zero and does not apply to arc, close, rect, or circle.

The optional --debug flag adds a debug point at the start of every drawing command in the body, numbered 1, 2, 3, and so on. Explicit debug statements are not included in that count. For example:

walk 0 0 90 --debug:
    line 20
    line 20
    line 20
    line 20

This marks the starting corners of the four edges as 1 through 4 in the browser preview.

walk distance: moves forward by distance in the current cursor direction after each drawing command or placement. walk dx dy: performs the same coordinate-relative movement as move dx dy. The three-value form performs that movement and then turns relatively by angle. Both actions happen after the final drawing command or placement as well as between items.

For example, each body entry in walk DX DY ANGLE: behaves like:

line E
move DX DY
turn ANGLE

A zero displacement is a true no-op and does not split the active path. This makes walk 0 0 angle: a concise way to turn after each edge while preserving one continuous logical path.

At the top level or inside a part, a walk may place parts:

walk --move 30 0:
    place triangle_up --label "A1" --tabs 3
    place triangle_up --label "A2" --tabs 2 3

Each place is one walk item. The part is placed at the current cursor, then the walk applies its movement and turn. A placement does not receive --stride; without --step or --move, consecutive placements use the same origin. Walk --debug numbers placement origins along with drawing-command starts. A walk containing place cannot be used inside a fabrication operation or another scope where placement is already prohibited. Walk turns change the movement heading but do not rotate placed parts.

A group can combine several statements into one walk item. Its children share the current step origin, the group restores that origin when it ends, and the walk advances only once after the complete group:

walk --move E 0:
    place triangle_down --label "B1"
    group:
        place triangle_up --label "B2"
        place triangle_down --label "B3"
    place triangle_up --label "B4"

Here B2 and B3 are placed together at E, 0, while B4 begins at 2 * E, 0. With --debug, the group receives one number at its starting point.

Walk bodies may contain drawing commands: line, score, tab, right, left, up, down, arc, bezier, close, rect, and circle. They may also contain place, group, debug, or debug label statements. A debug statement records the cursor at that exact point in the sequence; it does not apply an additional move or turn.

All direct walk statements must align at one indentation level; a nested group's children are indented beneath its header. Header expressions, including stride, are evaluated once when the walk begins. A walk may be used anywhere all of its children are valid. Drawing-only walks work in fabrication operations, parts, groups, measure: blocks, and sequential repeats; placement walks work at the top level and inside parts.

Repetition

repeat count: executes geometry sequentially. Every iteration begins with the position and angle left by the previous iteration:

repeat 6:
    line 20
    arc 5 60

This produces a rounded six-sided path. Sequential repeats may appear at the top level, inside a fabrication operation, inside a part, or inside a measure: block. Their bodies may contain geometry commands, debug, and group [label]: or walk ...: blocks. A top-level sequential repeat may also contain array repeats. After the block, result.* describes the final command of the final iteration.

Because placing a part does not move the cursor, a sequential repeat can use an array repeat to build rows and then move between them:

repeat 4:
    repeat 4 every WIDTH * 3:
        place hex_pair
    move 0 10

This produces four rows of four placed parts. The inner array leaves the cursor at the start of its row; only move 0 10 advances it.

The existing repeat count every spacing: form instead duplicates a prototype along x:

repeat 4 every 25:
    cut window:
        move 20 15
        rect 15 20

For both forms, the count is an expression that must produce a positive integer no greater than 10,000. For the array form, spacing is an expression and must be non-zero. The repeated prototype starts at the current document cursor. Its first copy uses the geometry as authored, and each later copy is offset by another spacing in x. An array repeat may contain fabrication operations, placed parts, or debug points. An array repeat cannot directly contain another repeat block.

Cursor debugging

Record the current cursor with an optional identifier label:

move 7.45 4.2
debug track_start

or:

debug

Debug points do not appear in SVG or PNG exports. In the browser preview they appear as clickable dots showing the label, authored coordinates, unit, cursor angle, source line, and any names assigned to the most recently rendered segment. Segment names survive intervening turn and angle commands, making them visible for debug statements inside a walk. Debug points inside parts and repeats are translated with their geometry. The debug display rounds the angle to two decimal places; the cursor retains its full internal precision.

Complete command reference

Syntax Effect
units mm\|cm\|m\|in\|ft Select authored units
coordinates page\|cartesian Select y-axis direction
scale numerator:denominator Set modeling scale
defaults: followed by thickness expression Set document-wide 3D thickness
defaults tab: Set document-wide defaults for tab options
defaults pattern: Set document-wide surface-pattern appearance
name = expression Assign a numeric variable
move dx dy [--zero] Move relatively, optionally resetting to the origin first
move --to name.p1\|start\|end\|center Move to a saved drawing point without changing the cursor angle or coordinate plane
line distance [--score] Draw forward as a cut, or score with --score
line dx dy [--score] Draw a coordinate-relative cut, or score with --score
bezier distance\|dx dy --control cx cy [cx cy] [--score] Draw a quadratic or cubic Bézier with explicit control offsets
bezier distance\|dx dy --tangent length [--control cx cy] [--score] Draw a Bézier starting along the cursor angle
score distance Draw a score forward in the current direction
score dx dy Draw a coordinate-relative score segment
tab distance [options...] [--off] Draw a tab forward, or its baseline when off
tab dx dy [options...] [--off] Draw a coordinate-relative tab, or its baseline when off
tabs number... / tabs none Select cut-operation segments to convert from lines into tabs
right distance Draw visually right
left distance Draw visually left
up distance Draw visually up
down distance Draw visually down
turn sweep_angle Add to the cursor angle without moving or drawing
angle absolute_angle Set the cursor angle without moving or drawing
label "text" Center a text annotation in the nearest operation, group, or document
group [label]: Run drawing commands, then restore cursor position and angle
add [--thickness expression\|default]: Merge child cut shapes; convert interior edges into scores; optionally override 3D thickness
subtract [--thickness expression\|default]: Remove subsequent closed shapes from the first shape; optionally override 3D thickness
group [label] scored: Convert duplicate cut lines in the group into one shared score
background "name": Add an experimental browser background directly inside a group
pattern kind: Tile vector surface markings over a part, cut, or group’s closed shapes
from named_line: Draw in a local plane based at the start of a named line
walk distance [debug]: Move forward after every drawing command or placement; optionally number their starting points
walk dx dy [angle] [debug]: Move, then optionally turn, after every drawing command or placement; optionally number their starts
walk [positionals...] [--step distance\|--move dx dy] [--turn angle] [--stride length] [--debug]: Use named, order-independent options after any positional values; --stride supplies omitted child lengths
arc radius sweep_angle Draw a tangent circular arc and update the cursor angle
close Close the active path
zero [dx dy] Reset the cursor, optionally moving from the origin afterward
zero --at named_line Persistently use a named line as the current local coordinate plane
rect width height [--radius value] Draw a rectangle with optional rounded corners at the cursor
triangle base height [--apex offset] [--rounded radius] Draw a triangle with an optional apex offset and rounded corners at the cursor
circle radius [start_angle sweep_angle] Draw a complete or partial circle centered at the cursor
cut [name\|--id string] [--thickness expression\|default]: Begin a cut block, optionally overriding 3D thickness
score [name|--id string]: Begin a score operation block
engrave [name|--id string]: Begin an engrave operation block
guide [name|--id string]: Begin a guide operation block
measure: Compute path results without emitting geometry
part name: / parameters: Define reusable local geometry with required or defaulted parameters
place name [arguments...] [--name value...] [--notabs] Place a part, optionally converting its tabs to lines
flow right: Place parts consecutively along x
repeat count: Execute geometry sequentially, carrying cursor state
repeat count every spacing: Duplicate operations along x
debug [label] Mark the cursor in the web preview

Square brackets in the table mean optional syntax; do not type the brackets. Every cursor and geometry command in the table may also include --name identifier.

Output behavior

SVG output is tightly fitted to the geometry and uses millimetres for physical size. PNG output has a white background, includes physical DPI metadata, and adds enough pixel padding to keep boundary strokes visible. Both formats use operation-specific colors and line styles.

The browser preview adds a 1 mm viewport margin, includes debug points in its bounds, and supports zoom from 25% to 400%. These preview conveniences do not alter exported files.

Printing from the browser hides the editor controls, grid, and debug markers, and uses the SVG's physical dimensions rather than the preview zoom level.

Python API

from cutwright import parse, parse_file, render_png, render_svg

document = parse("cut panel:\n    rect 100 50\n")
svg_text = render_svg(document)
png_bytes = render_png(document, dpi=300)
document_from_disk = parse_file("drawing.mac")

Current boundaries

The implemented language intentionally has no absolute positioning commands: use zero dx dy when a known origin-relative location is needed. Arbitrary freeform transforms beyond named-line coordinate planes, layers, automatic tabs, page nesting, and 3D operations are not implemented yet.

The file extension is .mac.

Liking designs

Sign in and click the heart on another user's design or a system design to like it. Click the filled heart again to remove your like. You cannot like your own designs. The number beside the heart is the total like count; other people's liking history is private.

My Liked Designs in the library lists the original designs, most recently liked first, with links to their owners. It always opens the current version. Liking does not grant access: unavailable or deleted designs disappear from this folder. The folder cannot be renamed or used as a save destination.

Use Public Designs and choose Most liked to explore popular public designs. User profiles also offer a Most liked sort. Hidden and private designs are excluded from public listings.

Cloning a design

Other people's designs open with read-only source. Use Clone to choose a name and one of your catalogs, then create your own editable design. Clones are private by default. The original design remains unchanged; likes do not transfer.

Each clone records its original design and a snapshot of that design's name, owner, and revision in the database. The snapshot remains if the original is later deleted. Cloning system designs records the same origin information.