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.

Use this guide to work in the browser editor and write makeacut scripts.

units mm

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, display numbered coordinate rulers along with the background grid, and save or open makeacut designs. The preview's Print… menu can print the current design or download it as PNG or SVG. On the hosted site, log in to use this menu; clicking it while logged out explains why login is required. With the 3D feature enabled, select 3D to prepare and display a model of the current source. The lower-right footer shows the displayed component's width × height in millimetres, followed by its material thicknesses. In 3D it shows width × depth × height, measuring the visible material; hiding an assembly's roof updates these dimensions. Preview padding, debug markers and editing handles do not add to the measurements. Millimetre measurements use at most two decimal places, and each distinct material thickness is listed once. 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 (1.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.

Assembly beveling is checked by default in 3D Printer and is disabled when the source has no assembly. It prints fitted copies of the selected wall instances, using the perimeter's joints and joining wall tops to a generated gable or hip roof's underside. Uncheck it to print the authored source components. Source definitions are unchanged. Preview, STEP, STL, and ZIP use the same selected fitting mode. Wall stock extends upward where necessary and trims to the roof, including bevels through its thickness. One generated roof per perimeter is supported.

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. Each completed Boolean result keeps its own holes, independently of sibling results. Thickness extends from each piece's starting depth, which defaults to Z=0. Use --z to place material at another depth; drawing a second piece at the same position does not stack it on top of the first automatically.

Live preview keeps the current drawing when you edit comments, blank lines, trailing whitespace, or repeated spaces outside quoted text. These source edits still count as changes for saving and autosave. Changes to indentation, quoted text, or executable statements refresh the drawing. Run always requests a fresh render.

Live preview waits briefly after you edit and updates to show your latest changes. The delay increases for designs that take longer to render. Run starts immediately.

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 plan for each component split across multiple pages. Whole components do not get a plan page. 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.

Component names and copy numbers, Matching joint labels, and Script labels can be enabled independently. Component names identify every printable piece. Matching J markers identify connected edges in solved assemblies, including wall corners, roof seams and wall-to-roof or wall-to-base contacts. Labels stay upright when parts rotate to fit a page, and the optional page plan explains the markers. Paper defaults enable labels; laser defaults disable them. Printing enumerates assembly wall copies individually and leaves out the extra combined wall layout so each required wall prints once.

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 design card lists its component and assembly counts below the description. Component counts match the editor's component selector, including generated assembly parts and Main when present; repeated quantities count once.

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. Recent designs and the last-opened catalog follow your account across browsers. Shared designs remain in the list only while you 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 saves your latest edits as a checkpoint in the design's history, including its preview. An older browser tab cannot silently overwrite 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.

To throw away changes since the latest checkpoint, open Save and choose Discard draft. After confirmation, the editor returns to the saved source and clears the working draft and browser-local backup. No new revision is created, and saved revision history is preserved.

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, ellipse, triangle, arch, circle, arc, or arcbox command 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 and ellipses 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. Closed arch bands have span, rise, and band-width handles. Dragging keeps all three dimensions positive and the rise at most half the span. Box-sized arcs (arcbox) have span and signed-rise handles. Dragging can reverse the bow while keeping the rise non-zero and its magnitude at most half the span. 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, subtract, and clip, handles follow the original command before trimming or the Boolean operation.

The toggle is unavailable on read-only designs. Handles and the inspector are excluded from printing and exports.

Editor shortcuts

Commands with a fixed set of values show a completion popup after the command and a space. This includes medium, units, z-units, and named scale presets. Type to filter, use Up/Down to choose, then Enter or Tab to insert; clicking an option also inserts it. Escape dismisses the popup. Completed values leave Enter and Tab available for normal editing. Numeric scale expressions are unaffected. For medium, type a space after a value to choose another; values already listed are hidden. Completion replaces only the current value, preserving other mediums, comments, and surrounding source.

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, scale, medium, and defaults declarations must appear before geometry. They cannot be changed inside a part.

medium value [value ...]

Declare the intended fabrication methods or mediums for a design:

units mm
medium laser-cutting 3d-printing paper

cut panel:
    rect 40 20

Choose one or more space-separated values:

Value Intended use
laser-cutting Laser cutting
3d-printing 3D printing
paper Paper or card craft
cutting-plotter Cutting plotter
cnc-routing CNC routing

The declaration is optional. Omission means not specified. Use at most one medium declaration, directly at document level before geometry, components, or assemblies. It has no colon or body. Repeated values within the declaration are ignored, preserving their first occurrence. Unknown values are errors and list the supported choices.

medium records author intent only. It does not change geometry, cursor, settings, previews, print defaults, available commands, or export formats. Multiple values can describe alternative methods or mixed construction, such as laser-cut walls with a 3D-printed roof. The declaration applies to the whole design, including selected components and generated assembly parts; individual components cannot declare their own medium.

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. Thickness and Z offsets default to physical millimetres, independent of the drawing's units and scale. Without a thickness declaration, thickness remains 5 physical millimetres. Thickness does not change 2D geometry, printing, or SVG/PNG exports.

Use z-units model to make thickness and Z offsets follow the drawing's units and modeling scale instead:

units cm
scale 1:2
defaults:
    z-units model
    thickness 0.8
    z 0.2
rect 4 2

This produces a 20 × 10 mm outline, 4 mm thick, starting at Z=1 mm. With z-units mm (the default), the same outline is 0.8 mm thick and starts at Z=0.2 mm. The setting applies to defaults, block overrides, parts, and Boolean results. Numeric part arguments acquire these units when used as thickness or Z; ordinary drawing coordinates keep using the drawing's units and scale. --thickness default still selects the document's thickness, and --z default still selects the document's Z value. An undeclared thickness is 1.5 mm in either mode. Only mm and model are accepted for z-units.

Existing hosted designs, working drafts, and revision history receive an explicit z-units model declaration during migration to preserve their output. For older local .mac files that need the previous behavior, add that setting to a general defaults: block before geometry.

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, z, z-units, 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 uses the enclosing scoped thickness default from a group, flow, distribute, or layout, or the document default when no scoped thickness applies. 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.

Depth offsets

z is the starting depth of material along the extrusion axis. It defaults to zero. A piece occupies z through z + thickness, so projecting details can leave empty space behind them:

defaults:
    thickness 1
    z 0

cut wall:
    rect 100 39

zero 0 39:
    cut cornice --thickness 1.75:
        rect 100 1

zero 100 39:
    cut return --z 1 --thickness 0.75:
        rect 3 1

The return occupies depths 1 through 1.75, with no material from 0 through 1. It still needs an authored connection to the cornice or another supporting piece. For a wall elevation, drawing Y represents building height; Z represents depth through the wall. Z does not change the 2D cursor or drawing coordinates.

Drawing blocks can override the enclosing depth with --z expression. Overrides are absolute, use physical millimetres by default (or drawing units and modeling scale with z-units model), and accept finite negative values. Nested blocks inherit their parent's depth, and exiting an override restores the enclosing depth. --z default selects the global defaults: value, even inside another override:

defaults:
    z 2

group --z 5:
    rect 20 20
    zero 30 0 --z default:
        rect 20 20

move 60 0
rect 20 20

Here the three pieces start at depths 5, 2, and 2. Overrides are supported on fabrication operations, group, add, subtract, clip, hole, coordinate blocks, repeat, walk, flow, measure, conditional branches, parts, and components. Parts inherit the placement context unless their own blocks override it. A part header can use a parameter in --z; component headers accept --z as well. Configuration blocks such as defaults, parameters, and pattern do not take this option. Primitive commands such as rect do not take --z.

Keep independent material at different depths in separate cut blocks. General add and subtract profile Booleans cannot combine different starting depths; an explicit result thickness does not flatten depth differences.

A plain hole: remains a through opening in its targeted material, including raised material. An explicit or inherited local --z bounds the hole's starting depth; its cutter thickness is inherited from each target. Only the intersecting depth range is removed. For example, with 1-unit-thick material starting at zero, hole --z 0.5: removes the upper half of its profile and leaves a recess floor. Holes still cannot declare their own thickness. Material drawn after a hole can restore the opening, and unspecified crossbar stock inherits its host's depth.

Interactive previews retain openings as sparse void regions. Enclosed disjoint openings become exact hole contours directly; overlapping voids and edge notches use exact geometry processing. Voids do not render as transparent meshes or fill the space behind raised pieces. STEP/STL exports preserve actual empty space.

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

All scripts use Cartesian coordinates: positive x points right and positive y points up. No coordinate-system declaration is needed or supported. move dx dy and line dx dy use these axes within the current local plane. The initial cursor is (0, 0); negative coordinates are allowed.

Angles start at 0° pointing right, with 90° pointing up. Positive turns and arc sweeps are counterclockwise in an unreflected plane. Rotated and reflected local planes transform the drawing and its axes together.

Image crop values remain pixels measured from the image's top-left corner; they do not use the drawing coordinate system.

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.

You can also select a named scale:

units m
scale "n scale"
cut platform:
    rect 24 2.4

This is equivalent to scale 1:160, producing a 150 by 15 mm platform. Names and aliases are case-insensitive; spaces, hyphens, and underscores are interchangeable. For example, "N", "n-scale", and "n scale" select the same preset. Figure names also accept a space before mm, such as "28 mm". Named scales use JSON-style double-quoted strings. Numeric ratios and expressions continue to work. Declare either form before geometry; scales cannot change inside a reusable part.

The editor suggests matching presets while entering a scale, showing their ratios and notes. Use Up/Down to choose, Enter or Tab to insert, and Escape to dismiss. An unknown name produces an error with spelling suggestions; it never selects a different scale automatically.

The 3D Printer dialog also displays a declared scale after Part thickness in the model details, including the resolved ratio for a named scale.

Named scales

The table is generated from the same built-in catalog used by the parser and editor. A released name always retains its ratio. Regional railway conventions have explicit names; unqualified N selects 1:160 and unqualified O selects 1:48. Railway gauge and modeling scale are different concepts; these presets select a dimension ratio and do not configure track gauge.

Wargaming millimetre sizes are nominal figure sizes. Their entries select the listed modeling convention, rather than calculating a ratio from a person's height. In particular, "15mm" selects 1:100, "25mm" selects 1:72, and "28mm" selects 1:56. Figure proportions, terrain, and game ground scales may use other conventions; specify a numeric ratio for those cases. scale "28mm" does not set the drawing units to millimetres. Material thickness remains physical millimetres unless you explicitly select z-units model in general defaults.

Name Ratio Category Aliases Notes
"t scale" 1:450 Railways "t"
"z scale" 1:220 Railways "z"
"n scale" 1:160 Railways "n", "n scale us", "n scale europe" Default N convention; use the UK or Japan entry when appropriate.
"n scale uk" 1:148 Railways "british n", "uk n", "n uk"
"n scale japan" 1:150 Railways "japanese n", "n japan" Japanese 1:150 convention; some Japanese models use 1:160.
"tt scale" 1:120 Railways "tt", "tt120"
"tt scale uk" 1:101.6 Railways "british tt", "tt3", "3mm scale" Traditional British TT: 3 mm to the foot; TT:120 uses 1:120.
"ho scale" 1:87.1 Railways "ho", "h0", "h0 scale" Uses the conventional rounded ratio 1:87.1.
"oo scale" 1:76.2 Railways "oo", "00", "00 scale", "4mm scale" 4 mm to the foot.
"s scale" 1:64 Railways "s"
"o scale" 1:48 Railways "o", "o scale us" US O convention; UK and continental Europe have separate entries.
"o scale uk" 1:43.5 Railways "british o", "uk o", "o uk" Uses the conventional rounded ratio 1:43.5.
"o scale europe" 1:45 Railways "european o", "o europe" Continental 1:45 convention.
"gauge 1" 1:32 Railways "gauge one", "1 gauge"
"g scale" 1:22.5 Railways "g" Selects 1:22.5; other garden railway ratios require an explicit scale.
"2mm" 1:900 Wargaming — Nominal figure scale; selects 1:900.
"3mm" 1:600 Wargaming — Nominal figure scale; selects 1:600.
"6mm" 1:285 Wargaming — Nominal figure scale; selects 1:285, not 1:300.
"6mm 1:300" 1:300 Wargaming "6mm uk" Alternative 6 mm convention.
"10mm" 1:150 Wargaming — Nominal figure scale; selects 1:150.
"12mm" 1:144 Wargaming — Nominal figure scale; selects 1:144.
"15mm" 1:100 Wargaming — Nominal figure scale; selects 1:100.
"20mm" 1:72 Wargaming — Nominal figure scale; selects 1:72.
"25mm" 1:72 Wargaming — Nominal figure scale; selects 1:72.
"28mm" 1:56 Wargaming — Nominal figure scale; selects 1:56.
"32mm" 1:48 Wargaming — Nominal figure scale; selects 1:48.
"54mm" 1:32 Wargaming — Nominal figure scale; selects 1:32.
"1:144" 1:144 Aircraft "1/144"
"1:72" 1:72 Aircraft / military models "1/72"
"1:48" 1:48 Aircraft / military models / dollhouses "1/48", "quarter inch dollhouse"
"1:35" 1:35 Military models "1/35"
"1:32" 1:32 Aircraft / military models "1/32"
"1:64" 1:64 Cars "1/64"
"1:43" 1:43 Cars "1/43"
"1:24" 1:24 Cars / dollhouses "1/24", "half inch dollhouse"
"1:18" 1:18 Cars "1/18"
"1:1200" 1:1200 Ships "1/1200"
"1:700" 1:700 Ships "1/700"
"1:350" 1:350 Ships "1/350"
"1:500" 1:500 Architecture "1/500"
"1:200" 1:200 Architecture "1/200"
"1:100" 1:100 Architecture "1/100"
"1:50" 1:50 Architecture "1/50"
"1:12" 1:12 Dollhouses "1/12", "dollhouse", "dollhouse scale", "one inch dollhouse"
"1:6" 1:6 Dollhouses "1/6"

Railway references: NMRA scale guide, British N examples, and Kato Japanese N figures. Wargaming convention examples: Warlord 28mm / 1:56 and GHQ 6mm / 1:285. The nominal figure mappings in the table are makeacut presets, not a universal standard for every manufacturer.

image alias:

In the hosted editor, declare an image from My Images before using its alias:

units mm

image the_building:
    name "Building 1"
    size 800 600
    width 80
    crop 0 0 0 0

group artwork:
    background the_building --mode fixed --at 0 0 --clip shape
    cut panel:
        rect the_building.width the_building.height

Image declarations belong at document level. The alias follows ordinary identifier rules and must be unique. Supply name "exact image name", id "image UUID", or both. Names are case-sensitive; duplicate ready images with the same name are ambiguous. When an ID is present, it is authoritative and the name is only a readable label; renaming the library image does not break the reference. An unavailable or foreign ID produces a warning and a placeholder, even if the name matches another image. You can copy the ID from the image's details in My Images. The editor's image inserter includes the ID, a readable name label, size, and crop (defaulting to crop 0 0 0 0, the full image).

size pixel_width pixel_height records the original processed image dimensions as two positive integers in pixels. These are fallback dimensions, independent of design units and modeling scale. When the image is available, its actual dimensions take precedence. When it is missing, the saved dimensions preserve crop calculations, aspect ratio, and drawing geometry. The editor and print preview display a light red checkerboard labelled with the image's name, or Missing image when no name is supplied, within the usual image bounds and clipping. The placeholder adds no fabrication geometry and is omitted from fabrication exports. Restoring the image restores its normal appearance on the next render.

Older declarations without size use estimated square source dimensions and warn that proportions may be inaccurate. For a legacy crop, the estimated square is large enough to contain the supplied crop. Add the original pixel dimensions to preserve the intended proportions. Invalid syntax, malformed IDs, ambiguous names, and invalid crop settings remain errors.

Supply either width expression or height expression to set the image's size in authored design units; the other dimension follows its aspect ratio. Both settings together are rejected. The resulting .width and .height are available in expressions, including reusable parts, and follow the normal units and modeling scale declarations. An 800 × 600 pixel image declared with width 80 above produces an 80 × 60 mm panel.

.pixel_width, .pixel_height, and .aspect_ratio are also available and describe the processed image, after orientation or HEIC conversion. Pixel counts and aspect ratio are dimensionless. Size expressions can use these properties, such as width the_building.pixel_width / 10.

Add crop x y width height to keep a rectangle of the source image before sizing:

image the_building:
    name "Building 1"
    size 800 600
    crop 100 50 400 200
    width 80

Crop coordinates are pixels in the full-size processed image, after orientation or HEIC conversion, measured from its top-left corner: x rightwards, y downwards. They do not follow design units, modeling scale, or Cartesian coordinates. The rectangle must lie entirely inside the source, with nonnegative x/y. A positive width or height sets that dimension directly. 0 extends from the crop origin to the source's right or bottom edge; a negative value trims that many pixels from that edge, after accounting for the crop origin. For an 800 × 600 source, crop 10 10 0 0 keeps 790 × 590 pixels, while crop 10 10 -200 -100 keeps 590 × 490 pixels. For a 959 × 1120 source, crop 15 2 -10 0 keeps 934 × 1118 pixels: 15 off the left, 2 off the top, and 10 off the right. The resolved width and height must be positive. Fractional pixels and numeric expressions are allowed. Use .source_pixel_width and .source_pixel_height in crop expressions to refer to the original processed dimensions. Crop is evaluated before size regardless of setting order; .pixel_width, .pixel_height, and .aspect_ratio then describe the cropped image. The example gives an 80 × 40 mm image in millimetres.

Crop preserves the library file and applies to every use of that alias, including all drawing modes and tiles. Separate aliases can crop the same source differently. background --clip still controls clipping against the group's geometry.

If width and height are omitted, the background defaults to filling its group's logical bounds, as with a quoted background name. Using .width or .height without declaring a size reports an error. Sized backgrounds default to keeping their declared dimensions, including when tiled across a larger group. An explicit background mode can fit, fill, or stretch the image to the group instead; it does not change the declaration's dimension properties or cut geometry. Clipping still uses the group's cut outlines or bounds.

Images remain private. Shared previews calculate geometry from the design owner's image dimensions but omit uploaded artwork for other viewers. Fabrication exports use image-derived geometry while omitting the image itself. Deleted images must be replaced or their references updated. The local editor has no image library.

Components

A design can contain no components or several named components. Wrap drawing instructions in a document-level component name: block:

FLOOR_HEIGHT = 20

part floor_1:
    rect 60 FLOOR_HEIGHT

component front_wall:
    walk 0 FLOOR_HEIGHT:
        place floor_1
        place floor_1
        place floor_1

component back_wall:
    place floor_1

The dropdown beside Render selects the component shown in both the 2D and 3D preview. Switching components restores a current cached preview or renders that component. Render regenerates the selected component. PNG and SVG exports use the selected component.

Use --quantity to declare how many identical finished pieces a component requires. It defaults to one and accepts an expression using shared variables:

N_SIDES = 2

component side --quantity N_SIDES:
    rect 30 15

The editor previews one piece and shows side ×2 in the component selector. The Print and 3D Printer dialogs give each required copy its own checkbox, side (1) and side (2). Uncheck either copy to omit just that piece. Print starts with all copies selected. Each selected copy gets its own named page set, including every page when a piece spans multiple sheets. Quantity must be a positive integer; designs using quantity declarations can require at most 1,000 pieces in total. --quantity and --z may appear together in either order.

Print and 3D Printer have independent component checkboxes and a Select all control. Paper and laser print previews identify each selected copy and plan its pages separately. The 3D dialog previews and exports only the selected copies together. Automatic packing tries different component orders and fills space beside larger pieces to reduce the required printing area, with a 10 mm gap between pieces. Each component keeps its orientation and internal arrangement. When multiple pieces are selected, Save as ZIP downloads a separate STEP or STL file for each selected component, using the chosen format and detail setting. The files are named after their components. Ordinary repeated copies share one geometry file in the ZIP; quantities.txt records the required count for each included component when quantities are in use. Assembly wall occurrences have numbered checkboxes even without --quantity. With Assembly beveling enabled, fitted occurrences have separate ZIP files because their cuts can differ. The print dialog lists those individual walls instead of the editor's generated wall packs, avoiding duplicate parts. ZIP exports preserve each component's original drawing coordinates.

Top-level variables, units, scale, defaults, image declarations, named pattern presets, and part definitions are shared. Keep shared declarations before drawing instructions. Each component starts at (0, 0) with angle zero; cursor state, local variable changes, named geometry, holes, and drawing operations stay within that component. Components organize output and are not reusable parts: use part and place for reuse.

When components are present, drawing instructions outside component blocks form an implicit Main component. Main is reserved for this purpose. Component names must be unique identifiers, and component blocks cannot nest. Indent with spaces. Designs without components retain their existing rendering and printing behavior.

Assemblies

Use a document-level assembly name: to describe wall perimeters and generate parts fitted to them. A perimeter arranges existing wall components; base, floor, and roof blocks use that solved geometry without resizing source walls.

units m
scale 1:160
defaults:
    thickness 1.5

component front:
    rect 9 2.8
component side --quantity 2:
    rect 10 2.8
component back:
    rect 9 2.8

assembly house:
    perimeter exterior:
        component front
        component side
        component back
        component side

    base foundation:
        from exterior
        margin 0.16

    floor upstairs:
        from exterior
        level 1.4
        clearance 0.2

Select house_foundation or house_upstairs to render, print, or export that part. Here the mitred outside wall footprint is 56.25 × 62.50 mm; the base's 0.16 m margin adds 1 mm on each side. The floor is 52.85 × 59.10 mm, allowing 0.20 mm clearance on each contacting edge. Both inherit the document's 1.5 mm thickness.

The component selector separates authored Components from Generated parts, grouped by assembly. Generated entries identify bases, floors, fitted wall packs, roof slopes, and named roof sections. Repeated joined assemblies include their instance names. For example, Foundation · Base selects house_foundation, and Main · Roof · Slope A selects house_main_a; hover over an entry to see its script identifier. These labels do not change identifiers used for exports. The rendered list reflects inferred flat roofs and omits collapsed roof slopes.

When 3D previews are enabled, the component selector lists assembly names in 3D Assemblies, above the generated parts. Select house to inspect the complete assembly as shaded solids with visible edges. Drag to rotate, scroll to zoom, Shift-drag to pan, and choose Fit to reset the camera. Show roof hides or restores the roof panels so you can inspect the walls and interior. Transparent makes surfaces 30% opaque with visible edges so you can see through walls and roofs; choose it again to restore solid surfaces. This toggle is also available in individual-part 3D views and only changes the preview. Repeated wall references appear at each assembled position, with wall tops automatically joined to the roof. Surface patterns appear as lines on walls and roof panels. Show roof also hides or restores the roof's pattern lines. Select a generated roof part (for example, house_main_a) to inspect its pattern flat in 2D or use the 3D printing preview to inspect physical grooves.

Selecting an individual part restores the previous 2D or 3D part view. Geometry editing and SVG/PNG export are unavailable in the assembly view. Choose Print → Assembly views for finished front, back, left, right and top drawings, with hidden edges concealed. Select the views, include or omit the roof, and choose overview sheets or one view per page. All views share one print scale: fit to page, Actual size (at design scale) (100%), or a custom percentage after the script's scale. Optional overall model dimensions are shown in millimetres. Actual and custom sizes pack views across as many pages as needed, choosing portrait or landscape automatically while keeping the views upright. Print or save as PDF through the system print dialog at 100% with no additional margins. Use the individual parts for fabrication printing and exports. Assembly visualization currently supports one perimeter and at most one roof. Independent perimeters require relative placement information before they can be visualized together. The centred assembly join described below also supports a house with one attached porch.

Settings belong to the block they control:

Generated parts can use a complete declaration without settings, such as base foundation, floor upstairs, or roof main. Add a colon only when a body follows. Every colon block needs a body; pass is an explicit no-op placeholder, including in settings blocks.

Block Settings
perimeter name: Ordered component instances, turn, joints, and optional layout auto, prefer front, minimize visible_joins
base name or base name: Optional from perimeter_name, level, thickness, margin, rim inside
floor name or floor name: Optional from perimeter_name, level, thickness, clearance
roof name or roof name: Infer a flat roof from level wall tops or a gable from matching wall profiles, specify rise or pitch, or use named section blocks; optional from perimeter_name, level (otherwise inferred), style gable\|hip\|cross-gable, ridge parallel wall_name, ridge position PERCENT%, hip ends, hip pitch, overhang eaves, overhang gables\|ends, thickness, joints miter, walls check\|fit, pattern preset_name
section name: inside a roof Required over wall_name ...; inherits roof defaults; overrides style gable\|hip, ridge, rise\|pitch, level, thickness, hip ends\|pitch, and overhangs; optional join merge, priority expression

The wall ordering, front priorities, span overrides, and joint rules described below apply inside each perimeter. A perimeter is one closed loop with 3–64 wall instances. An assembly may contain several independently solved perimeters and several parts referencing each one. References are local to the assembly and may precede their perimeter declaration. Perimeter and generated-part block names must be unique within an assembly; wall component references may repeat. When an assembly has exactly one perimeter, bases, floors, and roofs use it automatically if from is omitted, including when the perimeter is declared later. With multiple perimeters, specify from perimeter_name; the error lists the available names. An explicit from always selects that named perimeter.

Name an individual wall placement with --name identifier when reusing a component in different directions:

component boxy:
    rect 20 10

assembly front_porch:
    perimeter exterior:
        component boxy
        component boxy --name porch_side
        component boxy
        component boxy
    roof main:
        ridge parallel porch_side
        rise 5

boxy remains the reusable component; porch_side identifies only its second placement. Names use letters, digits, and underscores, starting with a letter or underscore. They must be unique within their perimeter and cannot match a component reference identifying another member there. Another perimeter may reuse the same names. Naming a member does not change the front direction, turns, geometry, or source component quantities. The first member still establishes the front; here the ridge runs front-to-back, with a gable at the front. --name combines with the other inline member options and also works in the legacy assembly shorthand.

Roof ridge parallel, hip ends, and section over references accept these local member names as well as component names. A member name selects one placement; a component name continues to select all its perimeter instances. Named members appear by name in wall diagnostics and assembly inspection. API perimeter and fitted-wall member metadata retain component and instance and add name for explicitly named placements.

A base defaults to the outside structural boundary. A floor defaults to the inside opening derived from the actual wall stock and solved corner placements, including unequal thicknesses, concave corners, and mitres. Door and window openings do not break these structural boundaries. Material projecting into the interior across any of a floor's thickness is excluded from its outline. A base includes outward stock at its seating face; an upper cornice does not enlarge a ground-level plate. Curved trim uses a conservative envelope with 0.001 mm flattening tolerance. A fit that removes the plate or splits it into disconnected pieces reports an error.

level uses drawing units and specifies the plate's top face in the assembled model, relative to the wall bottoms at zero. It defaults to zero. The plate extends downward by its thickness. margin uses drawing units; clearance and thickness use the usual thickness/Z units (physical mm unless z-units model is set). Margin and clearance must be non-negative and default to zero. Clearance is independent of kerf compensation. Omit thickness to inherit the document default, including the usual 1.5 mm fallback. If a base's level would put its solid through wall material, the operation reports that collision; use an inside-fit floor for a plate between the walls.

Each base or floor becomes a selectable component named assembly_part, with flat fabrication geometry starting at local Z=0. Its level controls only its position in the assembled model, so an upstairs floor still prints flat on the bed. Perimeter mitres generate assembly_perimeter_walls containing the bevelled printing copies. These names must not collide with authored components or other generated names. The assembly and perimeter themselves are not additional base components.

Courtyard loops and relative placement between perimeters remain future work. Named rectangular roof sections can join as described below. A single-perimeter assembly can be inspected by selecting its name below the separator in the component selector when 3D is enabled.

Joining a porch to a house

Keep the porch and house as separate assembly declarations. Name their contact wall instances with --name, then put the join inside the receiving house:

assembly front_porch:
    perimeter exterior:
        component boxy
        component boxy --name porch_side
        component boxy
        component boxy --name back
    roof main:
        ridge parallel porch_side
        rise 1

assembly house:
    perimeter exterior:
        component front_back --name front
        component side
        component front_back
        component side
    roof main:
        rise ROOF_RISE
    join front_porch.back to house.front

See the complete joined porch example for the component definitions. assembly.member selects a named wall instance, or a component used exactly once in that assembly's perimeter. Explicit names take precedence. On a rectangle, an unnamed front reference selects the first perimeter wall and an unnamed back selects the opposite wall. Both assemblies may be declared in either order.

The house stays fixed. Makeacut rotates the porch to oppose the contact walls, centres their outside spans, and aligns their ground levels. Physical wall thickness is included. The porch's connecting wall is omitted; its side-wall ends meet the house with square butt joints. The house wall opens across the porch's clear interior width, retaining stock above the porch roof and behind its side walls. Source components and standalone porch inspection are unchanged.

Move the join with optional inline offsets:

    join front_porch.back to house.front --in 50% --left 10%
Option Bare number Percentage
--in value Push into the house by this many drawing units Fraction of the original porch depth, perpendicular to its contact wall
--left value Move left from the centred position in drawing units Progress from centre to flush left: 0% centres, 50% moves halfway, 100% aligns the outside left walls
--right value Move right from the centred position in drawing units Progress from centre to flush right, with 100% aligning the outside right walls
--from-left distance Place the porch's left outside edge this distance inward from the receiving wall's left outside edge Not supported; use a distance in drawing units
--from-right distance Place the porch's right outside edge this distance inward from the receiving wall's right outside edge Not supported; use a distance in drawing units

Left and right are viewed from outside, facing the receiving wall, independently of the order or winding of its perimeter. Choose one of --left, --right, --from-left, and --from-right; signed side values may move in the opposite direction. Options can be written in any order and accept numeric expressions, including (100 / 2)%. Defaults are zero inset and zero sideways offset. Sideways travel is half the difference between the receiving wall's outside span and the porch's outside contact span. With a 6 m receiving wall and a 2 m wide, 2 m deep porch, the example pushes the porch 1 m inward and moves it 0.2 m left. --left 50% moves 1 m left; --left 100% moves 2 m and aligns the outside left walls. Alignment follows changes to either width. Equal widths leave zero sideways travel, so every valid sideways percentage has the same aligned position. Percentage bases use the original structural wall footprint, excluding roof overhangs and decorative trim; they never shrink after a join.

To specify a gap directly from an edge, use --from-left or --from-right. With units m, this places the porch's left edge 0.5 m inward from the house's left edge, regardless of the porch width:

    join front_porch.back to house.front --from-left 0.5 --in 50%

Use --from-right 0.5 for the same gap from the right. Zero aligns the outside edges; negative distances project beyond that edge. Positive distances continue inward across the receiving wall, and may pass its opposite edge while the shapes remain connected. Values use drawing units and accept numeric expressions, such as --from-left (WALL_LENGTH / 12). These edge options accept distances only, and combine with --in. Each join measures from its original receiving wall, including when an assembly has multiple joins.

Inset ranges from 0% through 100% of the source depth, inclusive. 100% places the entire original porch footprint on the house side of the contact plane. Covered walls and roof panels disappear cleanly from the scene and fabrication; exposed material is retained. Moving a join recalculates its openings and roof intersections, preserving the authored roof dimensions and slopes. It does not merely move the preview meshes.

Sideways percentages can continue past 100%: that value marks flush alignment, rather than a maximum movement. For a 2 m wide porch on a 6 m wall, --left 110% moves 2.2 m from centre and projects 0.2 m beyond the left edge. For example:

    join front_porch.back to house.front --in 90% --left 110%

Both percentages and bare sideways distances may extend beyond the receiving wall. Negative values move in the opposite direction. The structural footprints must still overlap or share an edge; touching only at a corner or moving the shapes apart reports a useful connection error. There is no fixed percentage cap and positions are not silently clamped. Exposed portions of the porch's back wall are retained, internal stock is removed, and the new exterior corners receive matching wall stock. Scene, fabrication and exports use the same parts. Flush joins refit old corners so their stock stays in the adjoining side-wall panels, rather than producing tiny separate strips from the former front-wall mitres. Fully covered wall occurrences are omitted from fitted printing. The assembled preview displays touching joined walls as one continuous surface, so aligned wall faces have no outlines or internal end caps at fabrication seams, including in Transparent mode. Individual wall parts remain available for fabrication and export. Each building keeps its original footprint and roof shape as the porch moves, including beyond a house corner. The house roof stays over the house; the porch roof moves with the porch. Only overlapping, hidden material is trimmed away. Roof valleys form where the original surfaces intersect, without stretching either roof across the combined footprint. Offset joins currently require zero roof overhangs. Vertical offsets (--up/--down) are not implemented.

Roof connection is automatic. A porch roof lying wholly below the house roof at the contact wall abuts the retained house wall. Roofs that meet use the shared roof-section solver for matching ridge and valley cuts. Different wall heights and roof slopes are retained. Select house in 3D to inspect the combined building. Generated porch roof parts are prefixed with the receiver, for example house_front_porch_main_a; standalone porch roof packs are omitted from the fabrication list to avoid duplicates. With Assembly beveling enabled, printing fits the same wall occurrences as the scene and omits covered stock. The generated house_exterior_walls contains all fitted joined walls.

Add more join statements inside the receiving assembly to attach more shapes:

    join front_porch.back to house.front --in 50%
    join front_porch.front to house.back --in 50%

Each statement places a separate copy, so the same source assembly can be reused on several walls or several times along one wall. Every offset uses the original receiver and source dimensions. All placed shapes are joined together, including overlap between extensions; hidden stock is removed once. The receiving house stays fixed, and every building retains its original footprint and roof shape. Multiple joins currently require zero roof overhangs.

Wall references accept explicit instance names and unambiguous component names. On a rectangle, front also defaults to the first perimeter wall and back to the opposite wall when that reference has no explicit name. Explicit references take precedence. Reused copies receive distinct generated names automatically: house_front_porch_main_a, house_front_porch_2_main_a, and so on. Numbering skips names already used by declared assemblies. Standalone front_porch inspection continues to show the original shape. See assembly-12-multiple-joins.mac.

Each assembly needs one rectangular perimeter with miter corners and one flat or ordinary gabled roof. To infer a flat roof from level structural wall tops, omit rise, pitch, and style; a bare roof main is sufficient. Flat roofs generate one horizontal panel and can join flat or gabled roofs. A gabled receiver must connect along an eaves wall, with its ridge parallel to the contact wall. A flat receiver can connect along any wall. The source contact span may equal or be smaller than the receiver's span. Flat-roof joins and lower-roof abutments require zero roof overhangs. Bases and floors declared in the receiving assembly fit the combined footprint. An inside rim follows the combined structural interior, with cutouts from the fitted walls. Declare these shared parts in the receiver; source assemblies currently support only their roof. For example, alongside the receiver's joins:

    base foundation:
        from exterior
        rim inside:
            thickness 1
            height 5

The base includes every joined extension automatically, regardless of whether it is declared before or after the joins. margin, level, and thickness retain their ordinary meanings.

Chained joins, vertical placement, alignment settings, and explicit connection policies are not yet supported. join needs no settings body or colon; an optional : body may contain pass as a no-op placeholder.

Gable roofs

A roof generates two stock-aware panels over a rectangular solved perimeter. Add this block inside assembly house:, alongside its perimeter exterior::

    roof main:
        style gable
        ridge parallel front
        rise 2.5
        overhang eaves 0.3
        overhang gables 0.2

These distances use drawing units, for example metres in a scaled building. Use pitch 30 instead of rise to specify the slope in degrees above horizontal; do not specify both. With two matching straight, centered gable wall profiles, omit both to infer the rise and ridge direction. Use roof main without a colon when no settings are needed, or roof main: followed by an indented pass. Opposite gables must agree in width, eaves height and rise, and the flat supporting walls must meet their eaves. Openings and smaller straight trim do not supply roof intent. When every wall has a continuous flat structural top at the same height, omitting style, rise and pitch instead generates one flat roof panel with zero rise. An explicit style gable still needs matching gables or a positive rise/pitch. For example:

assembly house:
    perimeter exterior:
        component front
        component side
        component back
        component side
    roof main

With level rectangular walls, select house_main_a to print the flat roof; there is no house_main_b. Its underside sits on the common wall tops and its thickness extends upward. Patterns, stock thickness, overhangs, wall fitting, 3D preview, STEP and STL use the same single panel. Eaves overhangs extend across the first wall's direction and gable overhangs along it; an explicit ridge parallel wall_name changes those reference directions. Variables such as ROOF_RISE only affect geometry when referenced in a setting.

Unequal flat tops report the conflicting wall heights; set level explicitly to fit them to a chosen horizontal plane. Asymmetric, curved, missing or conflicting gables require explicit ridge and rise or pitch settings unless all structural tops define the same flat roof plane. An explicit rise or pitch overrides the inferred slope; the ridge still follows matching gables unless explicitly overridden. Hip and cross-gable roofs require an explicit rise or pitch.

The rise is the vertical distance from eaves to ridge. A centered ridge spans half the outside wall span on each side, including stock at the corners. The eaves level locates the roof's underside at the outside supporting wall faces, relative to wall bottoms at zero. Omit level to infer it from the common flat structural tops of the walls parallel to the ridge. Gable peaks on the perpendicular walls do not affect it. For 2.8 m rectangular walls, this gives 2.8 m automatically; the side walls extend upward to form their gables. Inference uses the principal authored stock outline before openings and smaller trim, so doors, windows, shallow pockets, and decorative pieces do not raise or lower the eaves reference. Changes to the supporting wall height update the roof automatically.

Use ridge position to move the ridge across that span:

    roof main:
        rise ROOF_RISE
        ridge position 20%

The default is 50% (centered). With a ridge parallel to the front, 20% places it closer to the front and 80% closer to the back. The first wall listed in the perimeter defines the primary front; component names and additional --front visibility priorities do not change that reference. For a ridge perpendicular to the front, percentages run left to right when looking at the building from the front. Reversing the perimeter's turn direction preserves these conventions.

Percentages measure the outside wall footprint, excluding overhangs, and accept 0% through 100%, inclusive. Expressions such as ridge position (RIDGE + 5)% are supported. Direction and position can appear together as separate settings: ridge parallel side and ridge position 20%.

With rise, the ridge keeps its height and both pitches follow from its position. With pitch, that angle applies to the front-side slope (or left-side slope for a ridge perpendicular to the front); the other slope is calculated to meet it. Panels, wall fitting, patterns and STEP/STL share the unequal slopes and mating ridge cuts. Position also applies to hip roofs and can be inherited or overridden by named sections, always relative to the perimeter's primary front. The legacy style cross-gable requires centered ridges; use named sections for offsets. An inferred flat roof has no ridge, so position does not change its panel.

At 0% or 100%, the high edge sits on the corresponding outside wall. The zero-width slope disappears, leaving one sloping panel named house_main_a; there is no vertical panel or ridge joint. A stated rise stays fixed. With pitch, the angle applies to the remaining slope. Eaves overhangs extend that plane at both edges. Hip roofs retain their requested end panels, and a fully covered named section contributes no visible geometry.

An explicit level expression in drawing units overrides inference. If the supporting tops disagree or are sloping, curved, or stepped instead of flat, an error identifies the relevant wall instances and asks for an explicit level. The underside ridge is at level + rise; thickness extends outward normal to the slopes. Roof level differs from the top-face convention for base/floor plates. Roof eaves must remain at or above the wall bottoms.

Eave overhang is horizontal distance beyond the outside walls, measured at the underside. The underside continues down the slope through the overhang. Gable overhang extends along the ridge beyond the outside end walls. Both default to zero and must be non-negative. ridge parallel front names a wall member or component in this perimeter; omit it to follow matching gables, or otherwise use the first wall's direction. Repeated references are accepted when they run parallel; references occurring in perpendicular directions are ambiguous; use --name on the intended member and reference that name, or use a uniquely named wall component.

Thickness inherits document stock and may be overridden in thickness/Z units. style gable and joints miter are defaults; mitre is also accepted. The generated panels meet on an actual bevelled ridge, shared by 3D preview and STEP/STL. Their eave and gable edges are square-cut. Flat SVG/paper outlines show the stock silhouette; they do not convey the bevel through its thickness. Ridge edges carry matching A labels in the fabrication drawing, without adding material or engraving to the solid.

Apply a named surface pattern directly to generated roof panels:

    roof main:
        pattern corrugated_metal
        overhang eaves 0.25
        overhang gables 0.15

Declare corrugated_metal with an ordinary named pattern preset. On roofs, pattern X runs along the eave and pattern Y climbs the slope: a lines preset with angle 90 therefore runs from eave to ridge on both faces. Preset spacing, size, origin, angle, appearance and groove settings keep their normal units. Patterns are clipped to the generated panels and retained in individual panel previews, flat layouts and STEP/STL exports, including bevelled stock. Assembly inspection keeps its existing untextured view. Patterns can also be inherited or overridden by named roof sections. Drawing-guide follow presets cannot be used on generated roofs. See the inferred industrial roof sample for matching gables, corrugated panels, trim, openings and a locating base.

Walls automatically join upward to the roof underside. Start with ordinary rectangles at the eaves height: the assembly adds the gable or hipped wall tops and trims any excess, including bevels through the stock thickness. No level or walls fit setting is needed for ordinary flat supporting walls. Doors, windows, native curves, depth pockets, and restored bars retain their positions. The principal wall's siding pattern continues into the added stock with the same spacing and alignment; smaller decorative pieces stay at their authored heights. Source components remain unchanged; assembled inspection and 3D printing with Assembly beveling enabled use the joined copies.

Optional walls check instead validates authored wall material without extending or trimming it. Each wall must contact the underside without penetrating it. An opening crossing the finished supporting top still reports a gap. An explicit level remains available to override the automatically inferred eaves height.

Select house_main_a and house_main_b as individual roof parts. Select both in the printing phase for a complete visualization. To print the walls, select their ordinary component instances and leave Assembly beveling enabled. Explicit walls fit also exposes house_main_walls as a flat fabrication pack. Every generated component starts at local Z=0. A roof panel's bevel angle is measured from a square-ended panel cut; it is not the face-to-face angle.

Gable roofs support a rectangular footprint in either perimeter direction, with independent wall and roof stock thicknesses. Use named roof sections below for joined wings and valleys. Hipped ends are described next. Use the complete gable-roof sample to try roof panels, fitted walls with openings, and a matching base. The automatic-eaves sample uses existing gabled walls and omits level.

Hip roofs and single hipped ends

Use style hip to slope both ends as well as the two main roof faces:

roof main:
    style hip
    rise ROOF_RISE
    overhang eaves 0.3

Without ridge, a hip roof follows the longer outside footprint dimension. The end slopes inherit the main pitch, calculated from rise or the ordinary pitch setting. The result has four individual roof parts: house_main_a and house_main_b for the main faces, house_main_c and house_main_d for the ends. A square footprint with equal pitches makes four triangular faces meeting at one apex, with no ridge segment. hip ends both is the default.

To hip just one end, reference its member name or unique wall component name. The other end retains a vertical gable. This end wall must run perpendicular to the chosen ridge. For repeated components, use --name on the intended placement to distinguish the end. hip ends both always selects both ends.

roof main:
    style hip
    ridge parallel first_floor_front
    rise ROOF_RISE
    hip ends side_blank
    hip pitch 45
    overhang eaves 0.3
    overhang ends 0.2

This creates three roof parts, house_main_a, house_main_b, and house_main_c. hip pitch uses degrees above horizontal and controls only the selected end slope(s); it must be greater than 0 and less than 90. The main slopes still use rise or pitch. Both-end hips use the same hip pitch. The ridge is shortened by rise / tan(hip pitch) at each hipped wall face. If the selected hips would meet below the requested ridge height, Makeacut automatically steepens the end slopes just enough to fit. They meet at an apex when no ridge length remains. This keeps the requested rise, main slope, and ridge direction; an angle that already fits stays unchanged. Metadata records both the requested and fitted hip pitch, plus whether it was adjusted.

For hip roofs, overhang eaves also supplies the default end overhang. overhang ends overrides the two ridge-end overhangs independently; the existing overhang gables spelling is an alias, usable for either roof style. Both use horizontal plan distances at the roof underside. End edges can sit at different heights when their pitches or overhangs differ. Gable roofs keep their existing defaults: eave and end overhangs independently default to zero.

The gable rules for automatic perimeter selection, underside eave level, thickness, and walls check|fit also apply to hip roofs. For a named ridge, level inference uses the ridge-parallel structural tops; the selected hip walls automatically join to the additional underside planes. Rectangles at the eaves height work for full hips and single hipped ends, including the remaining gable. An explicit level can seat the roof below existing tall wall tops.

All ridge and hip joints are real stock-aware mitres, including unequal main and end pitches. Shared cutting planes create matching bevels across the full thickness. Fabrication outlines show each stock silhouette, with paired seam labels. Assembled inspection, fitted printing, STEP, STL, and ZIP export use the same individual panel solids. Hip roofs support one rectangular perimeter; other concave hip roofs and abutments remain future work. T-shaped gables use named roof sections below; the earlier cross-gable style remains available.

Complete examples: full hip roof and one hipped end.

Named roof sections

Put named section blocks inside one roof to give each wing an independent style and slope. Parent settings are inherited defaults, and each section may override its style, ridge, rise or pitch, eaves level, thickness, and overhangs:

    roof main:
        from exterior
        level EAVES_HEIGHT
        overhang eaves 0.3
        overhang gables 0.2
        joints miter
        walls fit

        section crossbar:
            over shoulder wing_end back
            style hip
            ridge parallel back
            rise ROOF_RISE

        section stem:
            over front stem_side
            style gable
            ridge parallel stem_side
            pitch 35
            join merge

over lists member or component names in this roof's perimeter. A member name selects one placement; a component name includes all its instances in that section's supporting region. Makeacut uses their solved stock footprints to find a rectangle aligned with the section ridge. Supporting walls must follow two perpendicular directions. The receiving section needs walls on all four sides; a joining wing needs three exposed sides and one open edge meeting an earlier section. Its open edge extends into that section, where Makeacut derives the intersection cuts and valley seams. The open edge must fit inside the receiving roof's rectangular span. A joining hip section omits a hip at the open ridge end so it can merge into the receiving roof.

Declaration order supplies default priorities 0, 1, and so on. Lower values lead; priority expression overrides the default without moving the block. The first touching section in priority order receives a wing, and earlier sections own coincident faces. join merge is the default and currently the only supported connection. Separate roofs and stepped wall abutments require future connection modes.

Each section must resolve to exactly one rise or pitch after inheritance. An explicit section pitch replaces an inherited rise, and vice versa. Omit a shared level to infer each section's eaves from its own supporting ridge-parallel wall tops. Section styles are gable and hip; cross-gable remains a shorthand for the earlier equal-pitch T-shaped case. from and walls check|fit belong to the parent roof and apply to the complete envelope.

Select house_main_crossbar or house_main_stem for a section's flat fabrication pack. Each contains its exposed roof panels, including disconnected pieces where another wing divides a slope. Select house in 3D to inspect the individual panels in place. Fitted printing, STEP, STL, and browser meshes share the same stock-aware seam cuts, including unequal section thicknesses. Outer edges use vertical cuts at the requested plan overhangs. Sections share the combined overhung outline so valley cuts continue through recessed corners even when their pitches differ.

Roofs support up to eight sections and a bounded intersection search. General nonrectangular section regions, courtyard loops, and join abut are not supported. The T-shaped house sample uses the mixed styles above, seven roughly equal 6 m sides, an 18 m back wall, and a matching T-shaped foundation.

T-shaped cross-gable roofs

Use style cross-gable over one T-shaped perimeter. The main ridge runs along the crossbar, and a perpendicular gable over the stem joins it with two valleys:

    roof main:
        style cross-gable
        ridge parallel back
        rise ROOF_RISE
        overhang eaves 0.3
        overhang gables 0.2
        walls fit

The solved outside footprint must be a rectangular crossbar with one rectangular stem projecting from its side. The stem may be off-centre, but its width must not exceed the crossbar depth. ridge parallel back identifies the crossbar direction; omitting it uses the first wall direction. Main and stem slopes share a pitch. rise sets the main ridge rise; the stem rise follows its own outside width at that pitch. Use pitch instead of rise to choose the angle directly. Eaves height, thickness, wall fitting, and perimeter selection use the ordinary gable rules. Rectangular wall stock is sufficient; gable tops and wall bevels are generated while preserving doors, windows, and patterns.

The five printable roof parts are house_main_a and _c for the two front crossbar pieces, _b for its back slope, and _d and _e for the stem slopes. Splitting the front slope keeps its parts connected even when both ridges reach the same height. When the stem ridge is lower, the front pieces share an additional straight seam above it. Paired ridge, valley, and straight seam labels appear in fabrication drawings. Mitres through the full stock thickness match in assembled preview, fitted printing, STEP, and STL.

Eave overhang applies to both sections' sloping sides; gable overhang applies to the crossbar ends and stem front. These distances are horizontal at the underside. Cross-gable outer edges use vertical cuts at these plan boundaries. L shapes, more wings, hipped crossbars, different section pitches, and roof abutments are not supported by this style.

For independently styled roofs over this footprint, use the named-section T-shaped house sample.

Inside locating rims

Add rim inside to a nested base block to generate a raised locating rim:

base foundation:
    from exterior
    rim inside

The rim's outside face follows the solved walls' structural inside boundary, so the walls sit on the base and abut the rim with zero clearance. Its thickness extends inward into the building; its height rises from the base's top face. The rim follows concave corners, unequal wall thicknesses, and mitred joints. Changing base margin does not move the rim.

Rim thickness defaults to the document's default thickness, including the usual 1.5 mm fallback. A thickness override on the base changes only the supporting plate. Rim height defaults to 10 physical mm (1 cm), independent of drawing units, model scale, and z-units.

Override either dimension with an indented block:

base foundation:
    from exterior
    thickness 3
    rim inside:
        thickness 2
        height 10
        cutouts auto

Explicit rim thickness and height use thickness/Z units: normally physical mm, or model units with z-units model. Both must be positive and finite. The block may specify just one setting, leaving the other at its default. The compact form rim inside: height 4 also works. Only one rim is allowed per base; floors do not accept rims. A width that closes the whole interior opening or cannot form a connected rim reports a source-located error.

The rim belongs to the same selectable base component. Its raised shape appears in 3D preview, and STEP/STL exports fuse it to the plate as one connected solid. The ordinary 2D view shows its nominal outer and inner stock outlines; the 3D view includes the actual opening profiles.

Rims automatically follow through-openings in their supporting wall components. cutouts auto is the default, including for the bare rim inside form. A doorway taller than the rim removes that section completely; a low window creates an opening at the same height, leaving rim material above and below. Openings above the rim have no effect. Profiles extend through the entire rim width, preserve native arcs and Bezier curves, and leave the supporting plate intact.

The openings come from evaluated wall material, so placed parts, repeats, Boolean cuts, and restored bars are respected. Material remaining at any depth through the structural wall stock blocks a through-opening: shallow pockets, engravings, and surface recesses do not create rim cutouts. Wall orientation, base level, model scale, and thickness/Z units are included in placement. Preview and CAD exports use the same exact cutter geometry. Unsupported floating rim fragments report an error with the rim's source location.

To retain a continuous rim, set cutouts none in its block. This restores the earlier rim behavior. auto and none are the only accepted cutout modes. Decorative projections and other generated parts do not otherwise reshape the rim. Choose rim and floor heights that leave room for each other. See the locating-base sample.

Assembly bases

The earlier syntax below remains supported as shorthand for one perimeter plus one outside-fit base. Prefer the explicit nested form above for new assemblies.

An assembly name: with wall instances directly in its body creates a selectable base component fitted underneath an ordered loop of existing wall components. makeacut uses their actual physical thickness, finds non-intersecting corner placements, and preserves the source panels. Concave outlines retain their wings and recesses.

units m
scale 1:160
defaults:
    thickness 1.5

component front:
    rect 9 2.8
component side --quantity 2:
    rect 10 2.8
component back:
    rect 9 2.8

assembly base:
    component front
    component side
    component back
    component side

Select base beside Render to see or export the generated plate. Here the mitred wall corners produce a 56.25 × 62.50 mm base. The walls and base both inherit the document's 1.5 mm default thickness. Changing the wall stock regenerates the outline. Omit assembly thickness to use the same default as ordinary components, including the usual 1.5 mm fallback when no default is declared. Set it only when the base should have a different thickness; it uses the usual thickness/Z units. margin adds a non-negative border in drawing units; it defaults to zero.

The first wall runs along +X. Each following wall turns +90° by default, a left turn. An assembly-level turn expression changes that default. A member's --turn expression overrides its incoming turn, including negative turns for recesses and zero for a straight continuation. The first member's turn specifies the closing corner from the last wall to the first. All turns must total +360° or -360°, and the physical loop must close without overlapping wall material.

The first component listed in the assembly always establishes the front direction. Every wall facing the same direction after applying the turns is also front, including recessed or projecting walls. Parallel walls facing the opposite direction are not front. This uses assembly order and orientation, not component names or the order of component definitions.

Use component name --front to add another front direction. That component and all walls facing the same way receive front priority, including walls listed earlier. Multiple members can use --front; the first member's front direction always remains included. For example, component side --front can prioritize a visible side façade as well as the automatically identified front. --front takes no argument and can be combined with other inline member options. Component references do not accept a colon or an indented body. There is no front: group syntax.

Comments have no layout meaning. Front surfaces are prioritized, then earlier components; equal valid layouts are resolved deterministically. Valid geometry and explicit joint constraints take precedence over front priority. The equivalent policy settings layout auto, prefer front, and minimize visible_joins can be stated explicitly.

Each component name is one wall instance regardless of the source component's --quantity. Put its options on the same line:

assembly custom_base:
    component front
    component side --turn 90 --over
    component back
    component side

Member --auto lets the solver choose a square-ended butt joint. --after makes the previous wall cover the new wall's end; --over makes the new wall cover the previous wall's end. These constrain placement and never interpenetrate or resize stock. A conflict reports the assembly's source location and closure-offset bounds.

The principal wall stock (the largest material contour by bounding area) supplies the horizontal span and thickness. Openings do not interrupt that span. To choose a span explicitly, use --span start end on the component reference. Both endpoints use the source drawing units. This is useful when trim projects beyond the structural wall. Each component retains its own elevation and depth. The bottom of the complete evaluated wall material seats at assembly level zero, including a separate lower section such as a brick plinth beneath metal siding. The largest section still determines the horizontal span and structural thickness.

Mitred joints

Perimeters default to joints miter (also spelled mitre), producing mating end bevels. Use joints auto for the square-ended butt-joint solver, or choose --auto, --after, --over, or --miter on an individual component reference. Square-ended butt joints support right angles; straight continuations remain square and need no bevel. Changing the joint policy can change the solved footprint for the same panel lengths; generated bases, floors, and roofs follow that solved footprint.

component wall:
    rect 20 10

assembly octagon:
    turn 45
    joints miter
    component wall
    component wall
    component wall
    component wall
    component wall
    component wall
    component wall
    component wall

The solver calculates the meeting planes using both walls' thicknesses. It only removes end material, retaining the authored stock extents. If the bevels consume a wall, the assembly fails instead of stretching it. The extra octagon_walls component contains a flat fabrication layout of the bevelled wall copies. In 3D Printer, select the individual walls and leave Assembly beveling checked to obtain fitted parts automatically. The original components remain available unchanged. octagon is the base.

Bevelled copies use the same exact solid geometry in the 3D preview and STEP/STL exports, including holes and pattern grooves. Their 2D outlines show the original stock; a flat SVG or paper print does not describe the bevelled end surfaces. This shorthand generates a base and flat printing layouts. Select the assembly name below the component selector's separator for its assembled 3D inspection view. This shorthand supports one closed perimeter per assembly, up to 64 wall instances. More constrained loops can use explicit corner overrides. The explicit nested syntax above also supports inside-fit floors and multiple independent named perimeters.

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, 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.

box width height [--name identifier]:

Declare a rectangular layout footprint and draw relative to its local origin. The box starts at the current cursor and follows the enclosing coordinate plane's axes. Width and height must be positive, finite expressions in authored drawing units. A box draws no material by itself:

part parapet:
    box 100 30 --name panel:
        rect panel.width panel.height
        zero -5 (box.height - 4):
            rect (panel.width + 10) 4

flow:
    place parapet
    place parapet

Each parapet occupies 100 units of flow width, although its cornice extends 5 units beyond each side. The cornices therefore overlap by 10 units when placed edge to edge. A flow gap adds to the declared footprint as usual.

box.width and box.height are read-only dimensions of the nearest enclosing box. Optional --name panel also exposes panel.width and panel.height throughout that box's body, including nested blocks. An inner box supplies its own box.* values while named outer dimensions remain accessible. On exit, the outer dimensions resume. A name may be shadowed by a nested box; the previous name resumes afterward. Box names and dimensions do not escape their body. Named parts receive dimensions through explicit parameters, for example place pillar --height box.height; they do not inherit the caller's box context.

The footprint runs from local (0, 0) to (box.width, box.height) and is the box's hard bound for flow, center, layout, and distribute. Geometry, placed parts, reserved space, and nested boxes inside it cannot enlarge or shrink that footprint. An enclosing group combines the footprint with its other children. Geometry outside the box still renders and exports in full; the box does not clip or resize anything. Boolean material operations continue to use the actual geometry. Parent labels and fitted group backgrounds also measure the footprint; background shape clipping still follows actual material.

Inside the body, zero returns to the box origin, and zero x y positions relative to it. Leaving the box restores the previous cursor position, heading, and coordinate plane. The box follows the enclosing plane's axes, independently of the cursor heading; use zero --angle ...: to rotate it. Boxes work inside parts, drawing operations, groups, repeats, and layouts. Cursor adjustments are allowed inside a box used as a flow item.

A colon requires a nonempty body. Use box 100 30: pass to reserve layout space without drawing. pass takes no arguments and changes no state.

zero [x y] [--angle expression]:

Establish a temporary local origin without defining or placing a part:

WINDOW_UP = 4
WINDOW_WIDTH = 7
WINDOW_HEIGHT = 12
CROSSBAR = 1

part window:
    zero 0 WINDOW_UP:
        hole:
            rect WINDOW_WIDTH WINDOW_HEIGHT
        zero (WINDOW_WIDTH-CROSSBAR)/2 0
        rect CROSSBAR WINDOW_HEIGHT
        zero 0 (WINDOW_HEIGHT-CROSSBAR)/2
        rect WINDOW_WIDTH CROSSBAR

The header's coordinates are offsets from the enclosing coordinate plane's origin, just like plain zero x y; they do not depend on the cursor's current position. Inside the block, zero returns to the new local origin, and zero x y positions relative to that origin. Omitting the coordinates uses the enclosing origin.

Add --angle to rotate the local axes in degrees relative to the enclosing plane. The default angle is 0, which preserves the enclosing axes:

zero 20 10 --angle 30:
    rect 12 6
    zero 4 2
    circle 1

To use a named drawing point as the local origin, replace the coordinates with --to name.point:

rect WIDTH DEPTH --name the_base
zero --to the_base.p2 --angle 90:
    place joiner

--to accepts the same point references as move --to, including numbered corners, segment endpoints, and circle centers. The drawing must already exist. The origin is the saved point's exact position, while --angle still rotates relative to the enclosing plane. Omit --angle to keep the enclosing axes. The two options may appear in either order; --to cannot be combined with positional coordinates.

Movement, shapes, placed parts, and repeats all use the local axes. Positive rotation follows the enclosing plane's positive angle direction, including inside reflected from planes. The block preserves that plane's handedness. zero resets the cursor's heading to the local x-axis; angle 0 also points along that axis. Nested blocks compose their offsets and rotations.

When the block ends, the previous cursor position, heading, and coordinate plane are restored. Geometry remains, and no new part or Boolean operation is created. Plain zero without a colon retains its existing behavior; --angle and --to are options on the scoped block only.

For an origin relative to the current cursor, use move dx dy: instead.

from named_line:

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


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, lines, 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, from, scoped zero, or scoped move, 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 and 90° points up. move, rect, ellipse, triangle, arch, 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 p1: start; p2: end start = p1; end = p2
triangle p1: base start at the cursor; p2: base end; p3: apex —
arch p1, p2: inner left and right spring points; p3: inner crown; p4, p5: outer left and right endpoints; p6: outer crown start = p1; end = p2; center
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
arcbox p1: start; p2: end; p3: crown start = p1; end = p2; crown = p3; 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
ellipse p1: positive x; p2: positive y; p3: negative x; p4: negative y, relative to its center 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
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 an unrotated plane. 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.

pass

Do nothing. pass takes no arguments and does not draw, move or turn the cursor, interrupt an active path, or change drawing settings. It is accepted wherever a statement or setting can appear, including component definitions, assemblies, perimeters, generated parts, roof sections, rims, defaults, patterns, images, and parameter blocks. Use it as an explicit placeholder:

if true:
    pass

part unfinished:
    pass

defaults:
    pass

assembly model:
    perimeter exterior:
        component front
        component side
        component back
        component side
    roof main:
        pass

Blocks that require geometry or placed parts still have those requirements. An image still requires a name or id, and a perimeter still requires a valid closed wall loop. pass does not supply missing geometry or required settings.

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.

move dx dy: or move --to name.point:

Establish a temporary local origin at an offset from the current cursor:

move -CORNICE_THICKNESS_DESIGN ( FLOOR_HEIGHT - CORNICE_HEIGHT ):
    place cornice --width ( TOTAL_WIDTH + CORNICE_THICKNESS_DESIGN*2 )

The offset follows the current coordinate plane's axes, including rotated or reflected planes. The block inherits those axes and preserves the cursor's heading on entry. The heading does not rotate the axes. Header expressions use the cursor and variables before entering the block.

Inside the block, the destination is local (0, 0). Plain zero returns there and resets the heading to the local x-axis; zero x y positions from that origin. Nested move: blocks offset from their current cursor, while nested zero: blocks offset from their enclosing origin.

move 100 50
move 10 20:
    rect 30 10
    zero
    circle 2
line 5

The rectangle and circle start at (110, 70), and the final line starts at (100, 50). On exit, the previous cursor position, heading, and coordinate plane are restored. Geometry remains, and no new part or Boolean operation is created. Movement starts a new path; paths do not connect across the block.

Use move --to name.point: to anchor the local origin at an existing named drawing point. It accepts the same point selectors as ordinary move --to, preserves the enclosing axes and cursor heading, and cannot be combined with positional coordinates. The block forms accept either two coordinates or --to; --zero and --angle are not block options. Use zero: for an origin-relative offset or explicit axis rotation.

Like other drawing blocks, scoped move accepts --z expression|default to set a temporary depth offset. The enclosing depth is restored on exit.

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.

Etching selected border segments

Add --etch to line, arc, arcbox, bezier, or close to retain that segment as a surface marking when adjoining shapes are merged. The segment still defines the material outline. An internal join that would automatically become a score becomes an etched seam for the selected interval; explicitly authored scores remain unchanged.

circle, rect, ellipse, triangle, and arch also accept --etch. On a complete shape, the option marks its whole outline, including rounded corners. On the partial circle radius start sweep form, it marks only the drawn arc.

circle 10 --etch
rect 20 12 --radius 2 --etch
triangle 20 15 --rounded 1 --etch

Use separate line and arc segments to select only some edges of an outline.

For an arch joined to a surrounding brick wall, mark only its outer arc:

part arch_bricks:
    arc ARCH_RADIUS -180 --angle 90 --name inner_arch
    line BRICK_WIDTH 0
    angle 90
    arc (ARCH_RADIUS + BRICK_WIDTH) 180 --etch
    line BRICK_WIDTH 0

    pattern bricks:
        follow inner_arch
        size 2 5
        stagger 0.5

Each segment is independently selectable. Add --etch to the inner arc or either straight end to mark those borders too. The option is supported on material cut borders, including inside parts, groups, and repeats; it cannot be combined with --score or applied to a hole, measurement, or tab segment.

Etched borders use the color, line-width, groove-width, and depth from defaults pattern:. Settings inside a pattern bricks: block apply to that brick pattern; use shared defaults to give the brick joints and border the same appearance. Tiling margins do not remove a deliberately selected border, and border etching does not add vertical side joints.

In 2D, the border is an engraving stroke. In 3D, its groove is centered across the seam on the joined material and clipped to openings and outside edges. Circular borders stay native arcs in SVG and CAD output. The groove retains the original material beneath it, subject to the normal depth checks.

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.

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.

band [name] --width expression [--align center|left|right] [--cap flat|vertical] [--name identifier]:

Turn a straight or curved construction path into a closed, cuttable strip. Width is measured perpendicular to each segment, in the document's units and modeling scale, independently of material thickness. The construction path is replaced by the finished band's boundaries.

band --width 1 --align center:
    line 0 10
    line 10 5
    line 10 -5
    line 0 -10

This draws a continuous roof border with an open bottom. Adjacent segments have mitred corners, and by default the two ends are flat and perpendicular to their segments. Very sharp outside corners are clipped at a mitre limit of five times the offset distance to avoid long spikes.

Mix line, arc, arcbox, and bezier commands in the same path:

band --width 2:
    line 10
    arc 10 90
    bezier 10 10 --tangent 5 --control 10 5

Curves are sampled into short straight edges for the finished boundaries, including SVG and 3D output. Sampling deviation is at most the smaller of 0.01 model millimeters and 0.1% of the band width. Flat caps follow the actual endpoint tangents. A full-circle arc makes a closed ring.

Name a band to make its entire construction path available to pattern follow:

group:
    band trim --width 4:
        line 10
        arc 20 90
        line 10
    pattern bricks:
        follow trim
        size 5 2
        margin 0.1

band --width 4 --name trim: is equivalent. Use an identifier for the name, and specify it only once. The pattern follows all connected segments with continuous spacing and fills the band's full width, including both sides of a centered path. Courses start at the band's left edge and progress toward its right edge in the original drawing direction; reflected parts reflect the courses too. Brick width is measured along the construction path. A move starts a separate path with its own pattern phase, and a closed border can end with a partial brick at its seam. at along across shifts the pattern's phase in these coordinates. Patterns are clipped to the finished material and retain the normal margin, hole, and depth behavior.

The name also exposes trim.start and trim.end for the construction endpoints (first and last path for a band with multiple paths). A band name cannot define a from plane; name an individual straight construction line for that purpose. Naming a band does not add its construction lines to the output.

Use --cap vertical to align each end with a vertical line through its construction endpoint, such as roof trim meeting vertical walls:

band --width .1 --align right --cap vertical:
    move 0 4
    line 9.295 1.05
    line 9.295 -1.05

The sloping edges extend or shorten to meet those vertical ends; perpendicular width and the mitred peak stay the same. Vertical means the Y direction of the coordinate plane where band starts, so caps rotate with scoped coordinate planes and placed parts. --cap flat explicitly selects the default caps. Closed paths have no end caps, so this option has no effect on them. On open paths, vertical caps require nonvertical terminal segments and exposed end edges. If a cap would cross another edge, reduce the width or lengthen the terminal segment; overlapping ends can be drawn as separate construction paths.

Alignment Placement
center (default) Half the width on each side of the path
left Full width to the left as you travel along the path
right Full width to the right as you travel along the path

Left and right are visual sides of the direction of travel, in either coordinate system. Reversing the path swaps them. In the Cartesian roof example above, left places the border outside the roof and right places it inside.

Add close to include the segment back to the first point and make a complete border around the opening. A path that already returns to its first point also forms a closed border. Wide bands can consume a small opening completely. move starts another construction path; overlapping strips within the same band are merged without internal cuts or scores.

The block supports line, arc, arcbox, bezier, move (including scoped blocks), turn, angle, close, zero, from, numeric variables, if/else, sequential repeat count:, and pass. Other shapes, scores, and --etch are not supported inside the construction block. Width must be positive, and at least one segment must have non-zero length. Zero-length segments alongside real segments are ignored.

On exit the cursor position, angle, and coordinate plane are restored. Path results and explicitly named construction segments remain available, including for move --to references. Only straight lines can define from planes. The source controls remain editable in the preview.

Bands work inside parts, groups, fabrication operations, Boolean blocks, repeats, and walks. Enclosing thickness and pattern settings apply to the finished shape; --z can set its starting depth. Bands produce ordinary closed boundaries in SVG, PNG, and 3D output. They are not supported inside measure: or directly as flow items; place a part containing a band for flow layout.

add [--thickness expression|default] [--pattern name]:

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.

Use --pattern name to apply a named pattern preset to the merged material. It uses the block's entry cursor and coordinate plane, and clips to the final outline, including holes. It can be combined with --thickness in either order.

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 and scale. 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] [--pattern name]:

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.

Use --pattern name to apply a named pattern preset to the remaining material. It uses the block's entry cursor and coordinate plane; removed areas receive no markings. It can be combined with --thickness in either order.

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 local coordinate planes 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.

clip [--z expression]:

Use the first closed cut shape as a mask, trimming every later child to its filled area. The mask itself produces no cut or material. Each later child is clipped independently: overlapping children remain separate pieces, and each keeps its operation kind, name, thickness, depth, and attached patterns.

clip:
    rect 40 30
    score crease:
        move -10 15
        line 60

This produces a 40-unit score line inside the rectangle, without cutting the rectangle. Open cuts, scores, engravings, and guides are trimmed without adding mask edges. Closed cut shapes remain closed, with new fabrication cuts along the mask boundary where needed. These are the actual output shapes in SVG, PNG, the 3D preview, and CAD exports.

Each ordinary closed outline is one child, even inside a cut: or group: block. A first-child place part_name uses the entire placed part's material footprint as one mask, including all its pieces and holes. Overlapping pieces contribute their combined area; they do not cancel each other. Scores, engravings, patterns, and material settings on this mask are consumed along with it. To retain a part's details and material settings, put the clipping boundary first and place part_name afterward.

A nested add: or subtract: result also counts as one compound child, including its islands and holes. Use one of these as the first child for a compound mask:

clip:
    subtract:
        rect 40 30
        move 15 10
        rect 10 10
    zero -5 5
    rect 50 20

The later rectangle is trimmed to the mask's outer boundary and retains the mask's rectangular opening. Geometry outside the mask disappears. Separate later children do not need to overlap each other; use add: explicitly when you want to merge the clipped pieces.

clip: restores the cursor position, angle, and coordinate plane on exit. It supports parts, repeats, groups, coordinate blocks, and walk:. The optional --z sets the children's starting depth; child overrides still apply. Set thickness on the children or through defaults. The mask's thickness and depth do not affect the output. Holes authored among later children act locally on those children, without removing material outside the block. Labels and debug markers remain authoring annotations.

pass is accepted anywhere in the body and changes no state. A mask with no later geometry, or an explicitly empty Boolean mask, produces no geometry. A colon requires an indented body; a missing mask or an open mask is an error. clip: is not supported inside measure:. The separate background --clip option continues to control background-image clipping.

hole [name]:

Declare closed geometry that removes material wherever it is placed. Unlike subtract:, a hole does not need its base shape inside the same block. This lets a reusable part carry its own opening:

part window:
    hole opening:
        rect 20 30
    # Add the crossbars back after removing the opening.
    zero 9 0
    rect 2 30
    zero 0 14
    rect 20 2
    cut cornice:
        zero
        move -2 -3
        rect 24 3

add:
    rect 100 50
    move 10 10
    repeat 3 every 30:
        place window

The wall gets three windows with four open panes each. The crossbars remain in the wall, while the cornices contribute ordinary solid geometry. See examples/hole-window.mac for a version with adjustable dimensions.

Hole children are combined into one void. Nested add: and subtract: blocks can shape that void, including islands that retain material. Separate holes remove their combined area; overlapping holes do not cancel. A hole crossing a material edge makes a notch, and a hole outside the material has no effect. Hole boundaries become actual fabrication cuts, including in SVG, PNG, STEP, STL, and the 3D preview.

Operations execute in source order within their containing material assembly. A hole removes material that exists at that point. Closed cut shapes drawn after it add material back; a later hole can remove that material again. Draw the wall first, then place its windows. Placing a window before drawing the wall allows the later wall to fill the opening. A hole with no existing material has no effect.

A part carries an ordered sequence of operations. Placement replays that sequence against the surrounding assembly, including through nested parts, coordinate-plane rotations, flow, walks, and repeats. Each repeated placement executes its whole sequence before the next placement. The window's opening therefore removes wall material, and its later crossbars restore material in that opening. An unspecified bar thickness inherits the material it joins.

Use add: to keep a panel and its windows in one material assembly. Once the block completes, its holes do not affect material outside it. Ordinary group: blocks organize drawings and restore the cursor; they do not consume holes. At document level, holes act on previously drawn intersecting material pieces. In an assembly using holes, closed cut shapes contribute material by union; open cut lines and marks retain their fabrication roles. At document level, completed Boolean blocks and placed pieces with explicit thickness retain separate material ownership, even when they touch or overlap the wall. For example, a window's add --thickness 2: cornice remains a separate 2-unit-thick piece while its ordinary crossbars inherit the wall's thickness. Later holes can cut both pieces. Inside an explicit enclosing add:, incompatible joined thicknesses remain errors unless that block supplies a result thickness override.

Material drawn before the holes keeps its declared or default thickness. An adjacent cornice with its own thickness does not change the wall's default. When a part contains holes, placement preserves which drawing commands restore material after a hole, so each floor can keep its own wall and cornice stock.

subtract: retains its explicit first-base, later-cutter semantics. Holes inside it remove the base once it exists and do not count as positional operands. Nested add: and subtract: results remain complete material shapes.

Like group:, hole: restores the cursor position, angle, and coordinate plane on exit. Its optional name labels the void. Children must be closed cut outlines; put scores, engravings, guides, backgrounds, and patterns outside the hole. A hole inherits surrounding material thickness and cannot set its own thickness. hole: is not supported inside measure:.

group [label] [scored] [--thickness expression|default] [--pattern name]:

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.

Use --thickness to set a local default for material drawn inside the group, including placed parts and repeats:

group wall --thickness 2:
    rect 40 20
    move 50 0
    cut detail --thickness 3:
        rect 10 10

The wall is 2 units thick and the detail is 3 units thick. Explicit child cut, add, and subtract thickness options take precedence. Nested groups can set their own default; leaving a group restores the enclosing thickness. --thickness default selects the document default, even within another group. The option follows the optional label and scored modifier, and can be combined with --z. It does not change how shapes join or how holes inherit the surrounding material's thickness. Use add --thickness ...: to explicitly set the thickness of a merged result.

Use --pattern name to apply a named pattern preset to the group's closed cut shapes. It can be combined with --thickness and --z. For local pattern overrides, put a pattern --use name: block inside the group instead. See Reusable pattern presets.

Thickness is the total height measured from the piece's z. A 5-unit arch over a 2-unit wall at the same z projects 3 units above the wall. Assembly preserves these different local heights, including when holes are applied; pieces using the same thickness still join normally.

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.

reference image_alias [--fade percentage]

Display a declared image as a drawing reference at the document or component origin, with a guide rectangle around its bounds:

image the_building:
    name "Building 1"
    width 80

component front:
    reference the_building
    move 10 20
    rect 30 40

Use reference directly at document level or directly inside a component. At document level its anchor is (0, 0); inside a component it is that component's (0, 0) and follows the component in combined layouts. In designs with components, a document-level reference belongs to Main; it is not shared by every component. Earlier move, zero, angle, and turn commands do not reposition it. The drawing cursor, heading, current path, and results are preserved. Nested drawing blocks and reusable parts do not support reference.

The alias must already have an image declaration with a width or height. The reference uses its crop and declared dimensions, with normal units and modeling scale. Its anchor is the bottom-left corner. It has no placement options.

References use fixed image sizing and bounds clipping, include their entire rectangle in preview bounds, and create no cut geometry or 3D material. In the 2D preview, the material fill above the reference defaults to 80% opacity. --fade specifies the percentage made transparent, from 0 to 100; the default is 20. Cut, score, engraving, and guide lines, labels, and the reference image keep their normal opacity:

reference the_building             # 20% fade: 80% material opacity

Use reference the_building --fade 50 for 50% opacity, --fade 0 for opaque material, or --fade 100 for fully transparent material fill. Decimal values and numeric expressions are supported. The setting applies to all material in the containing document or component, including material drawn before the reference command. Other components retain their own settings in combined layouts. Multiple references in one document or component must use the same fade value, including the default for references without --fade. Fading affects only the 2D preview; printing, exports, and 3D are unchanged.

Images appear only in browser previews; their guide outlines follow the usual guide visibility and export behavior. Use background inside a group for image-backed material, custom positioning, fitting, or tiling.

background image_alias|"name" [--mode kind] [--at x y] [--clip kind] [--tile]

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


group artwork:
    background "placeholder" --mode fill --at 0 0 --clip shape --tile

    cut panel:
        rect 80 50

The quoted value is the exact, case-sensitive name of an image in your hosted My Images library. For example, upload an image and name it Building 1, then use background "Building 1" --mode fit inside a group. Missing names and duplicate names report an error; rename duplicate images to choose one unambiguously. Renaming or deleting an image requires updating designs that use its name. The demo names placeholder and sample_art use the bundled background-1.jpg when your library has no matching image. Uploaded backgrounds are private and are omitted when someone else views a shared design, even if the image's redistribution checkbox is checked.

An unquoted value, such as background the_building --mode fixed, refers to a previously declared image the_building:. A quoted value always refers to a library name, even if it matches an alias. See image declarations.

Drawing modes are selected with --mode:

Mode Behavior
fixed Keep the image declaration's width and height, independently of the group
fit Fit the whole image inside a box with the group's logical width and height, preserving proportions and centering unused space
fill Fill that box, preserving proportions and cropping from the center as needed
stretch Match the box's width and height independently, allowing distortion

The default is fixed for an image alias with a declared width or height, and fill otherwise. Explicit fixed requires a sized image declaration; a quoted library name or unsized alias has no authored size to keep. Other modes use the group's logical width and height even when the image declaration supplies a size. Modes affect drawing only: image properties such as the_building.width remain the declaration's dimensions.

--at x y positions the image box's anchor in the group's coordinate plane using authored units. Fitting changes the box's size, not its bottom-left anchor. Positive y 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. The material mask belongs to the artwork group: holes inside that group remain transparent, while independent pieces placed outside it render as opaque white material above the image. Open cuts and score/engrave/guide lines do not cover the image with a fill. --clip bounds retains rectangular logical-bounds clipping. --clip none allows the image box to extend outside the group, expanding the browser preview as needed. These settings are independent of drawing mode: fill still crops the image to its placement box even when group clipping is disabled.

--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. Options may appear in any order and --at accepts numeric expressions. 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.

The background image itself does not create 3D material. A cut inside its group does: a full rectangular cut panel can cover holes in another part at the same position. For an image used only as a drawing reference, use:

reference the_building

The reference positions and clips the image without creating a solid in 3D or CAD. Keep cut when the panel is an actual piece to fabricate.

Backgrounds use command syntax only. Older background name: blocks report an error with the command syntax to use instead. Move their settings onto the command as --mode, --at, --clip, and --tile; replace noclip with --clip none.

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. Overlapping pieces retain their own pattern targets and thicknesses. Changing another piece's thickness does not erase the pattern from a raised surface.

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
stones size width height, optional seed integer 5 × 4

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.

Generated patterns have a 50,000-line budget per design or component, with the same limit on each pattern's generation and clipped output. Etched strokes have a separate 5,000-stroke budget per design or component and a 5,000-segment limit after curve tessellation per etching block. Bézier follow guides retain a separate 5,000-sample complexity limit. CAD groove exports also enforce a 1,000-line limit per part, including side joints. If a pattern exceeds a limit, increase its size or spacing, or reduce the patterned area.

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. 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.

Following a named curve

Add follow name to bend a pattern along a previously drawn named line, circular arc, quadratic/cubic Bézier, or band. The guide supplies its position and direction; angle cannot be combined with follow. The guide can be a cut, score, engraving, or guide segment. It does not need to be a material boundary. Names on rectangles, ellipses, full circles, and cursor commands are not curve guides. An arch --name name exposes its inner arc as the guide for follow name. A named arcbox exposes its circular curve as a guide. A named band exposes all its construction paths and its full width; see band for its course placement and connected-path behavior.

units mm

group arch:
    arc 20 -180 --angle 90 --name inner_arch
    line 5 0
    angle 90
    arc 25 180
    line 5 0

    pattern bricks:
        follow inner_arch
        size 5 5
        stagger 0.5
        groove-width 0.4 mm
        depth 0.3 mm

size width height measures width along the guide and height perpendicular to it. On a circular arc, rows extend outward from the guide as concentric curves, and end joints point toward its center. Brick width is measured on the guide: bricks widen in outer courses. Successive rows retain their usual stagger. The final brick can be partial when the guide length is not a multiple of the brick width.

For a Bézier, rows offset away from its initial bend and stay on that side through inflections. A straight guide extends rows to its right. Reflected placement reflects the whole pattern. Guides with zero length or an undefined interior tangent cannot be followed.

With follow, at along outward shifts the lattice along the guide and perpendicular to it, in authored units. The default is at 0 0. Marks stop at the guide's endpoints and cover its outward side, then clip to the finished material. Hole clipping, margins, appearance, aligned side joints, parts, repeats, and component layouts retain their normal behavior.

All pattern presets accept follow. Curved pattern strokes use line segments with a 0.01 mm approximation tolerance, shared by paper, laser, surface preview, and recessed 3D output. Material outlines retain their native curves. Curved strokes count toward the existing pattern and 3D groove segment limits; increase brick size or spacing if a pattern is too dense.

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
sides none\|aligned none Continue top joints down outside edges in 3D

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.

stones creates irregular adjoining polygons with shared joints, using a seeded Voronoi layout. size controls the average stone width and height, not the exact dimensions of each stone. seed defaults to 0 and accepts integers from 0 to 4294967295, including expressions. It is supported only for stones and is independent of units and scale. Changing the seed changes the arrangement; resizing the face reveals more of the same arrangement. The layout follows the pattern's at and angle, moves with placed parts, and clips to the finished material using the usual margin and hole rules.

For a thin roof, keep the joints shallow so a continuous backing remains:

units mm
defaults:
    thickness 1

component roof:
    group:
        rect 50 30 --name the_base
        pattern stones:
            size 5 4
            seed 42
            groove-width 0.4 mm
            depth 0.2 mm
            margin 1

Stone joints are straight polygon edges; this preset does not add raised pebbles or rounded stone relief. Paper and laser output use the joint paths; 3D output recesses them into the top face. A dense stone layout can reach the groove segment limit sooner than bricks; increase size when needed.

Patterns work inside reusable parts and retain their placement transforms. Placed patterned parts can be used in flow: 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.

Reusable pattern presets

Define a preset at document level with pattern kind --name name:. The name must be a unique identifier. A definition stores settings without drawing geometry, adding markings, or moving the cursor. Declare it before its first use; for parts, it must exist before the part is placed.

pattern bricks --name wall_bricks:
    size 3 1
    at 0 0

part main_top:
    group --pattern wall_bricks:
        rect 30 6

part main_bottom --pattern wall_bricks:
    rect 30 4

place main_top
move 0 10
place main_bottom

group --pattern name: applies the preset to the group. add --pattern name: and subtract --pattern name: apply it to the resulting material. part name --pattern preset: applies it to the whole part without a wrapper group. These forms use the same clipping and material ownership as inline patterns, including holes and later subtractions. Patterns move and rotate with placed parts.

To customize one use, replace the header option with an inline pattern --use name: block and list only the settings you want to override:

part main_top:
    group:
        pattern --use wall_bricks:
            at 4 0

        rect 30 6

This uses size 3 1 from wall_bricks and overrides its origin with at 4 0. The preset and its other uses stay unchanged. The override block works wherever an inline pattern works, including directly inside a part or cut. It may have no settings to apply the preset unchanged. Avoid also adding --pattern to the same block unless you intend to combine two patterns.

The repeat origin and angle use the receiving block's local coordinate plane; the cursor position at the preset definition has no effect. Setting expressions, units, and scale are evaluated at definition time. Local override expressions use the variables, units, and scale at the use, including part parameters. follow name resolves a previously drawn guide in the receiving scope.

Local settings take precedence over explicit preset settings, which take precedence over the current defaults pattern: appearance. Built-in defaults fill any remaining settings. A local size replaces inherited spacing, and local spacing replaces inherited size; declaring both locally is an error. The merged settings must still be valid for the preset's pattern kind, and angle cannot be combined with follow.

Existing unnamed pattern kind: blocks continue to apply directly to their containing geometry. Multiple named presets can use the same built-in kind.

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.

Use sides aligned to continue each top joint that reaches an outside edge vertically down the full material thickness. Joints line up on straight, circular, and Bezier boundaries, including open archways created by subtraction. Closed hole walls are excluded. Side grooves inherit color, groove-width, and depth; their depth is measured inward from the local side surface. Grooves that reach through the local material width or split a piece report a source diagnostic. Side joints use the final clipped top strokes, so a positive margin leaves the outside edge clear and prevents side continuation. Tiny disconnected fragments near corners or arch endpoints are discarded in grooved previews and STEP/STL exports. Each fragment must have an XY bounding footprint of at most 0.25 mm² and span at most 1 mm in either direction; their combined bounding footprints must not exceed 1% of the main piece's bounding footprint. These limits use physical output dimensions, regardless of scale or stock thickness. The editor warns when a side-groove check detects larger disconnected pieces. Reduce groove-width or depth, shift the pattern with at, or override inherited side grooves with sides none on the affected pattern. This advisory check is limited to 256 side joints per pattern on each unbanded part; depth-banded stock and denser patterns are checked during Grooves preview and STEP/STL export.

defaults pattern:
    sides aligned

part tunnel:
    subtract --thickness 3:
        rect 60 36
        move 30 8
        circle 12
        zero 18 0
        rect 24 8
    pattern bricks:
        size 8 3
        stagger 0.5

place tunnel

The setting can be declared on individual patterns or in defaults pattern:; sides none disables an inherited setting. Side patterns affect the 3D preview and STEP/STL output. Paper, SVG engraving paths, and PNG output retain their XY patterns. This version creates one course through the thickness, with no horizontal side joints or side staggering.

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 component (or per design without components) and 10,000 groove segments per material part for grooved 3D output. Selected components keep their individual limits when printed or exported together. Increase size or spacing when a density diagnostic appears; details are never silently omitted.

arc radius sweep_angle [--angle initial_tangent_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.

Use --angle to set the initial tangent explicitly, in degrees relative to the current coordinate plane. This has the same effect as an angle command immediately before the arc. It overrides the incoming cursor angle without moving the starting point. The ending cursor angle is the initial tangent plus the sweep. An explicit initial tangent can also be adjusted in the geometry inspector using its Initial tangent angle handle.

arc 10 90 --angle 45  # start tangent at 45°; end tangent at 135°
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. A positive sweep is counterclockwise in an unreflected plane.

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.

arcbox span rise

Draw an open circular arc using its endpoint span and crown height, without calculating a radius or sweep. The cursor is the starting spring point; the other endpoint is span units along the current coordinate plane's positive x axis. The crown is halfway across the span and rise units above the springline. The incoming cursor heading does not rotate the box; use a local coordinate plane such as zero --angle ...: to rotate it.

arcbox 40 10 --name curve

From the origin, this starts at (0, 0), ends at (40, 0), and reaches its crown at (20, 10). Positive rise bows toward positive y; negative rise bows toward negative y. Span must be positive, rise must be non-zero, and abs(rise) cannot exceed half the span. This is a circular arc, not an ellipse. Its radius is span * span / (8 * abs(rise)) + abs(rise) / 2.

The command draws only the curve. It moves the cursor to its endpoint and sets the heading to the ending tangent, so a following one-value line continues along that tangent. It joins an active path and publishes the same result.* measurements as arc. Add close to draw the straight baseline:

arcbox 40 10
close

Use --name for start/p1, end/p2, crown/p3, and the circle's center. A named arc supports pattern follow but cannot define a from plane. Expressions, units, modeling scale, rotated/reflected planes, parts, repeats, measure, cut/score/engrave/guide operations, and --etch are supported. Use it inside band for a strip following the curve:

band --width 1:
    arcbox 40 10

arch span rise --band width retains its existing closed-band behavior. arcbox is a reserved command name; use another name for variables, parts, or part parameters.

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 Cartesian axes 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 and Cartesian axes.

space width height

Reserve an invisible rectangle at the cursor for layout. It creates no cut, score, engraving, or material. Like rect, it follows the local coordinate plane, units, and modeling scale, independently of the cursor heading. The cursor position and heading stay unchanged outside flow.

flow:
    place left_panel
    space 10 0
    place right_panel

Both dimensions are required numeric expressions. Zero dimensions are allowed, so space 10 0 reserves a horizontal gap and space 0 5 reserves a vertical gap. Negative dimensions extend in the opposite local direction, as with rect. Inside flow, the cursor advances by the dimension along the flow axis: positive values create gaps, negative values move backward to create overlaps, and zero leaves the cursor unchanged. Repeated flow bodies also accept space.

Space contributes to the logical bounds used by flow, center, and layout, including when placed through a part. Its bounds combine with drawn geometry; it can enlarge a part's occupied area but cannot shrink it. A part can contain only space. For example, this part occupies 30 × 20 while drawing 20 × 20:

part padded_panel:
    rect 20 20
    space 30 20

Reserved regions are excluded from visible drawing bounds and fabrication exports. space does not accept rectangle drawing options such as --radius or --etch, and cannot be used inside measure.

rect width height [--radius value]

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

move 10 20
rect 30 40

Positive width extends right and positive height extends up. Negative width or height is allowed and extends in the opposite direction. A zero width or height draws nothing and preserves the cursor, heading and active path. Named corner references remain available even when corners coincide.

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, for a nonempty rectangle, 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. Rectangles with a zero dimension remain empty even with a positive radius.

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.*.

ellipse width height

Draw a closed oval fitting the same bounding box as rect width height. The cursor anchors the box's corner; its position and heading stay unchanged. The ellipse follows the current coordinate plane, independently of cursor heading.

move 10 20
ellipse 100 60 --name panel

This oval occupies the box from (10, 20) to (110, 80) and is centered at (60, 50). Both dimensions are full outer dimensions, not radii. They accept numeric expressions, drawing units, and modeling scale. Negative dimensions extend in the opposite local direction, as with rect; neither may be zero. Equal absolute dimensions produce an exact circle within the box.

Use --name to reference .center and the four outline extrema: .p1 at positive local x, .p2 at positive local y, .p3 at negative local x, and .p4 at negative local y, measured from the center regardless of dimension signs. Use --etch to etch the complete border. Complete ellipses are supported; there are no start-angle or sweep arguments. Like other closed shapes, an ellipse does not replace result.*.

Elliptical outlines use tangent cubic Bézier segments with a maximum deviation of 0.000001 physical mm from the ellipse. Their box dimensions are exact. Boolean operations and fabrication exports retain these curves rather than replacing them with a polygon.

subtract:
    ellipse 100 60
    move 10 10
    ellipse 80 40

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 up. 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.

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.

arch span rise --band width

Draw a closed circular arch band. The cursor marks the left endpoint of the inner arc, and the opening spans span units along the current plane's x axis. rise measures from this springline to the inner crown. --band is required and sets the radial distance between the inner and outer concentric arcs. The band ends are straight radial sides.

arch 60 12 --band 6    # shallow arch
move 80 0
arch 40 20 --band 5    # semicircular arch

Span, rise, and band width must be positive. Rise cannot exceed half the span; equality gives a semicircle. All three accept numeric expressions in the current units and follow the modeling scale. The inner radius is span * span / (8 * rise) + rise / 2; the outer radius adds the band width. Band width is a dimension in the drawing plane, separate from material thickness.

Positive rise bows upward (positive y) in an unrotated plane. The arch follows the current coordinate plane independently of the cursor heading. It preserves the cursor position, heading, and result.* values. It supports parts, repeats, boolean operations, and --etch, and retains native circular boundaries in SVG and fabrication output.

Use --name to expose its inner arc as a pattern guide:

group brick_arch:
    arch 60 12 --band 6 --name inner_arch
    pattern bricks:
        follow inner_arch
        size 5 6

This creates a continuous arch band with radial brick joints. The existing pattern can end with a partial brick; exact brick-count fitting is not yet supported. Named points include both spring points and crowns, as listed in Named drawing points.

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 is counterclockwise in an unreflected plane. 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.

Use ^ for powers: radius^2 squares a radius, 2^3 is 8, and 2^-2 is 0.25. Fractional exponents are supported, such as 9^0.5 for 3. Powers take precedence over multiplication, division, and leading signs: -3^2 is -9, while (-3)^2 is 9. Chained powers group from the right: 2^3^2 means 2^(3^2) and is 512. Undefined powers (including 0^0 and zero to a negative power), non-real results, and numeric overflow are errors.

Assignments inside a part are local to that placement. They can use document variables and part parameters, and are evaluated again each time the part is placed. They do not change the caller's variables.

Shape command keywords cannot be used as variable, part, or part parameter names. This includes triangle, arch, ellipse, and band, like rect and circle. Existing variables, parts, or part parameters using these names must be renamed, for example to arch_band or band_width. Operation names and drawing labels may still use these words. The words 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 variable followed by a spaced parenthesized expression is two arguments: rect door_width (wall_width / 2). Keep a function's opening parenthesis next to its name when a variable has the same name: length(3 4).

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

Use matching triple double quotes (""") or triple single quotes (''') for multiline comments. Everything between them is ignored, including commands, indentation, and # characters:

"""
Front elevation notes
rect 999 999  # temporarily disabled
"""
cut:
    '''This comment can also stay on one line.'''
    rect 120 50

Comments can appear beside commands. Close them with the same quote style; comments do not nest. Triple quotes inside ordinary quoted text stay literal.

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
floor(value) Round down to an integer (toward negative infinity)
ceil(value) Round up to an integer (toward positive infinity)
design_to_z(value) Convert a design distance to thickness/Z units
z_to_design(value) Convert a thickness/Z value to design units

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)
whole_panels = floor(available_width / panel_width)
panels_needed = ceil(required_width / panel_width)

Both floor and ceil take exactly one argument. For negative values, floor(-2.3) is -3 and ceil(-2.3) is -2. Integers stay unchanged.

Functions can be nested:

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

Converting design and thickness/Z units

Use design_to_z(value) when a drawing distance supplies a thickness or Z offset. Use z_to_design(value) when a thickness or Z value supplies a drawing distance, such as the width of a slot that fits another printed part.

Both functions take one numeric argument and account for the current units, scale, and z-units. With the default z-units mm, design_to_z multiplies by the physical millimetres per design unit; z_to_design divides by that factor. With z-units model, both return the input unchanged. Zero and negative values are supported, so the functions also work with offsets.

units mm
scale 1:100
stock = 2
slot_width = z_to_design(stock)    # 200 design units prints as 2 mm
layer_z = design_to_z(300)        # 300 design units prints as 3 mm

cut --thickness stock --z layer_z:
    rect 1000 slot_width

The functions are inverses within normal numeric precision and work in numeric expressions and conditions, including inside parts and components. Set units, scale, and z-units before evaluating conversions; variables retain the values calculated when assigned. Inside a defaults: block, conversions use that block's z-units setting regardless of its line order.

Current cursor

These read-only values are available in numeric expressions and conditions, including before any geometry is drawn:

Value Meaning
cursor.x Current cursor x coordinate from the current local origin
cursor.y Current cursor y coordinate in the current local coordinate plane
cursor.angle Current heading in degrees relative to the local x-axis, from 0 up to but excluding 360

Coordinates use authored units, including when modeling scale is active. They follow the current local axes inside from, zero ...:, and parts. Restoring a scope restores the cursor values too. Every expression reads the current state, including after move, zero, angle, and turn.

To reset one coordinate while keeping the other:

zero 0 cursor.y    # Reset x, keep y
zero cursor.x 0    # Reset y, keep x

Both forms also reset the heading, as usual for zero. Assign a cursor value to an ordinary variable to save a snapshot, such as saved_x = cursor.x. Unlike result.ax and result.ay, cursor coordinates follow moves that do not draw anything. The name cursor is reserved.

Results from drawing

After line, arc, arcbox, bezier, 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 current local coordinate plane
result.ax Endpoint x coordinate from the current origin
result.ay Endpoint y coordinate in the current local coordinate plane

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, turn, angle, arc, arcbox, bezier, close, zero, and pass. 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

Placing a part that produces no geometry gives a warning at its definition and skips that placement. It does not move the cursor or add spacing in a flow. Use pass for an unfinished part's body; a colon still requires a body.

A part header can include --pattern preset_name to apply a named pattern preset to all of its closed cut geometry. The option works with a parameters: block or header parameters, and can be combined with --z in either option order.

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.

A parameter defaulted to none accepts a numeric selection. For example, doors none defaults to an empty selection; --doors 1 selects one position, and --doors 1 4 selects two. Use in(value, selection) in a condition to test membership. Selections can be forwarded to other parts by parameter name. Ordinary numeric parameters still accept one value.

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.

A placed part with closed cut outlines keeps its own material and holes, even when positioned inside another piece. Its outline does not automatically become a hole in that piece. Use a placement inside subtract: to remove its shape explicitly. Each repeated or nested placement retains its material boundaries. Open-only parts can still join other open outlines into one closed boundary.

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

--thickness expression|default sets a scoped thickness default for inline shapes and placed parts, using the same rules as group --thickness. Explicit thickness settings inside children take precedence. default uses the document default, and the enclosing thickness resumes after the block. Thickness follows z-units, independently of flow direction and spacing:

flow --gap 2 --thickness 3:
    rect 10 8
    rect 25 12

flow: places children edge to edge along the positive local x axis using each child's logical bounds. It aligns the child's leftmost logical edge with the cursor, then advances by its full logical width. flow --horizontal: is the explicit form of the same command:

part short_side:
    cut:
        rect 10 8

part long_side:
    cut:
        rect 25 12

flow:
    place short_side
    place long_side
    place short_side
    place long_side

Use flow --vertical: to place parts along the positive local y axis:

flow --vertical:
    place short_side
    place long_side
    place short_side

Horizontal flow is the default. Vertical flow proceeds along positive y (upward). Both axes follow the enclosing local coordinate frame, including rotation and reflection. The two axis flags cannot be combined or repeated. flow right: remains supported as an alias for horizontal flow.

Add --gap expression to leave equal space between children, with no leading or trailing gap:

flow --gap 5:
    place short_side
    place long_side
    place short_side

The gap defaults to zero and accepts a finite, nonnegative expression in the current authored units, following the current scale. Use flow --vertical --gap 5: for vertical spacing. The direction and gap options may appear in either order; each may be specified only once.

Each placement in a repeat gets the gap automatically. A nested flow counts as one child and uses its own gap internally, defaulting to zero rather than inheriting its parent's gap. pass, empty repeats, and nested flows without logical bounds do not add gaps. Explicit space statements remain additional cursor adjustments and do not count as children for automatic spacing.

Use repeat count: inside flow to repeat placements along the selected axis. Each place advances the cursor separately, including multiple placements in a repeat body. Repeats may be nested, and repeat.position has its usual one-based value within each repeat. repeat.first and repeat.last identify the first and last iterations; when using explicit space for gaps, use if not repeat.last: to skip a trailing gap. With --gap, this is automatic. For example, build a stack of floors with:

N_FLOORS = 3

part floor:
    rect 40 10

flow --vertical:
    repeat N_FLOORS:
        place floor

Insert space width height to reserve a gap without drawing. Horizontal flow advances by the width; vertical flow advances by the height:

flow --vertical:
    place short_side
    space 0 5
    place long_side

Use a negative dimension along the selected axis to overlap the next part:

flow:
    place pillar
    space -0.8 0
    place archway

This overlaps their logical bounds by 0.8 drawing units. Zero leaves the cursor unchanged. A flow may contain only space, and placed parts may use space to enlarge their logical bounds or preserve intentional padding.

Nest flows to combine rows and columns without defining an extra part:

flow --vertical:
    flow:
        place short_side
        place long_side
    flow:
        place long_side
        place short_side

Each inner flow is one item in its parent. The parent aligns its leading logical edge with the cursor and advances by the inner flow's combined logical width or height, including reserved space and excluding tab overhang. The other axis keeps its original alignment. When the inner flow ends, the parent continues along its own axis. Flows may nest multiple levels, use either direction, and appear inside parts and repeated bodies. A nested flow containing only pass or zero repeats leaves the cursor unchanged.

Flow accepts inline geometry, drawing blocks, place and space statements, nested flow blocks, repeats, conditionals, and pass. Each inline shape is a separate item; a drawing block such as group: or cut: is one item measured by its combined logical bounds. For example, no extra part definitions are needed to arrange two rectangles:

part floor3:
    flow:
        rect 0.9 1.3
        rect 0.7 1.1

place floor3

move and zero, including their block and named-reference forms, are not allowed directly inside flow. This also applies through conditionals, repeats, and fabrication operation bodies. Put cursor adjustments inside a group: or box W H:; they affect the block's internal geometry, while flow places the whole block as one item:

flow --gap 2:
    group:
        move -3 1
        rect 6 4
        zero 5 0
        circle 2
    rect 8 5

Placed parts keep their normal internal cursor behavior. Use space to adjust flow spacing directly. Keep connected path commands together inside a drawing block so they form one flow item. Sequential repeat count: and conditionals contribute each selected child individually; a spaced repeat (repeat count every spacing:) is one item containing the entire array. Use pass as an explicit placeholder for an unfinished flow. Placeholder blocks and annotations without bounds do not move the cursor or add automatic gaps.

Every child with bounds must have nonzero logical width or height along the parent's selected axis. Negative coordinates and offsets from the part's origin do not create overlaps or leading gaps: horizontal flow aligns the lowest local x coordinate; vertical flow aligns the lowest authored local y coordinate, then advances by the full logical height. The other axis keeps its original alignment. Bands contribute their finished boundaries. Tabs are measured using their original baselines rather than their protruding cut outlines, so tab overhang does not introduce spacing. Use space inside a part to reserve intentional padding.

This replaces origin-based flow placement: existing designs that relied on overhangs overlapping a previous part now need explicit negative space. Ordinary place continues to use the part's origin and leaves the cursor unchanged. Flow leaves the cursor at the end of its final placement or space.

center --width expression [--height expression]:

Center the whole indented composition within a width, a height, or both. The box starts at the cursor's position on entry and follows the enclosing local coordinate axes. For example, this centers a window opening and its arch together across the wall:

INNER_WIDTH = 100
WINDOW_WIDTH = 20
WINDOW_HEIGHT = 30
WINDOW_UP = 12
WINDOW_ARCH_RISE = 5

rect INNER_WIDTH 60
center --width INNER_WIDTH:
    if 1: zero 0 (WINDOW_HEIGHT / 2):
        hole:
            rect WINDOW_WIDTH WINDOW_UP
    zero 0 (WINDOW_HEIGHT / 2):
        group --thickness 2:
            arch WINDOW_WIDTH WINDOW_ARCH_RISE --band 2

The body is measured as one composition, preserving the shapes' positions relative to each other. Logical bounds include offset shapes and arch bands; fabrication tabs use their original baselines. Centering translates geometry without resizing it. A composition wider or taller than the box overhangs equally on both sides of that axis.

--width centers horizontally and preserves the body's vertical offsets. --height centers along the local positive y axis. Supply both to center along both axes:

center --width 100 --height 60:
    rect 20 10

center positions its contents within the specified width or height, but does not reserve that space. Its layout bounds come from its contents, regardless of the supplied dimensions. If you need the full area reserved—for example, when placing the result in a flow—use space:

part centered_panel:
    space 40 20
    center --width 40 --height 20:
        rect 10 5

This part occupies 40 × 20 in layout, with the 10 × 5 rectangle centered inside it. Place space outside the center block so it reserves the full area without affecting how the visible contents are centered.

At least one dimension is required. Dimensions must be positive, finite expressions in the current units and modeling scale. The body must contain geometry or reserved space. Nested zero commands use the centered local origin. When the block ends, the previous cursor position, heading, and coordinate plane are restored. Holes still cut the surrounding material, and nested Boolean, thickness, and depth settings retain their usual behavior. Names and variable assignments created inside remain available afterward, as with a zero ...: block. center can be used inside parts, drawing groups, fabrication operations, sequential repeats, and flows. In a flow, the whole centered block is one item; flow aligns its leading logical bound as usual. It cannot be used inside measure:.

Distributing children

Use --thickness expression|default to set a scoped thickness default for all children, including placed parts: distribute --span 100 --thickness 3:. Explicit child overrides take precedence; default uses the document default. Thickness follows z-units and the enclosing setting resumes after the block.

distribute arranges its children across a specified span with equal gaps between their logical edges. It measures each child's width automatically, so children can have different sizes:

WINDOW2_GROUP_WIDTH = 100

part window2_shutter:
    rect 10 30

part window2_window:
    rect 20 30

distribute --span WINDOW2_GROUP_WIDTH:
    place window2_shutter
    place window2_window
    place window2_shutter

The children start at x = 0, 40, and 90, leaving equal 30-unit gaps. The first child's logical edge starts at the current cursor, and the last child's far edge ends at the span. Unlike layout, which repeats its whole body, distribute places each child once. Use flow --gap expression: when you know the desired gap, or distribute --span expression: when you know the space to fill.

--span is required and must be positive. --margin defaults to zero and reserves equal clearance at both ends. For two or more children, the gap is (span - 2 * margin - total_child_size) / (child_count - 1). A single child is centered between the margins. Exact fits with zero gaps are valid; children that cannot fit produce an error reporting their logical sizes, the required span, and the shortfall. Children are neither resized nor overlapped to make them fit.

Horizontal distribution is the default; --horizontal makes it explicit. Use --vertical to distribute upward along the local positive y axis, measuring heights instead of widths. Children keep their orientation. Span and margin follow the current units and modeling scale, and the enclosing coordinate frame supplies rotation and reflection.

Inline shapes and placed parts each count as one child. A group:, fabrication operation, nested flow:, layout, or nested distribute counts as one child measured by its combined logical bounds. Sequential repeats contribute their individual children in order, and conditionals contribute only the selected branch. A spaced repeat (every) is one child containing its whole array. A space width height statement reserves an invisible child and participates in spacing. Fabrication tab overhangs are excluded from measurement, just as in flow and layout.

pass, empty repeats, and placeholder blocks without logical bounds do not consume gaps. An empty distribution draws nothing. move and zero belong inside a group:, a box W H:, or a placed part rather than directly inside distribute. Assignments are local to the distribution. The body is evaluated once, in child order, before the measured children are positioned; cursor values used during evaluation reflect the children packed with zero gaps. Each child's geometry evaluates in its own local frame.

The enclosing cursor and heading are restored afterward. Distributions can be used inside parts, components, groups, coordinate blocks, flows, layouts, and repeats. As with layout, put fabrication operations inside the distribution rather than putting the distribution inside an operation.

Distributed layout

Use --thickness expression|default to set a scoped thickness default for every copy, including inline shapes and placed parts: layout --span 100 --count 3 --thickness 3:. Explicit child overrides take precedence; default uses the document default. Thickness follows z-units and the enclosing setting resumes after the block.

layout repeats its whole body into a specified span. By default, it keeps fixed margins at both ends and equal gaps between the copies. It measures each copy's logical bounds, so you do not need to calculate the spacing or supply the part width:

TOTAL_WIDTH = 100
DOOR_MARGIN = 10
N_BAYS = 3

part door:
    hole:
        rect 20 30

rect TOTAL_WIDTH 40
layout --margin DOOR_MARGIN --count N_BAYS --span TOTAL_WIDTH:
    place door

The three doors start at x = 10, 40, and 70. The span includes both margins, the doors, and the gaps. For identical items, the gap is (span - 2 * margin - count * item_size) / (count - 1). Each margin is the clearance to the logical edge of the first or last copy, even if the body's geometry is offset from its local origin. Protruding fabrication tabs use their original baselines for this measurement, as they do in flow:.

--span is a required expression. --count defaults to 1, so a single item can be centered with layout --span 100:. --margin defaults to zero. Horizontal layout is the default; --horizontal makes it explicit. Use --vertical to distribute along the local positive y axis, measuring each item's height instead of its width. It does not rotate the items:

part window:
    rect 8 10

layout --vertical --span 50 --count 3 --margin 5:
    place window

Here the windows start at y = 5, 20, and 35 in authored coordinates. Vertical layout proceeds along positive y (upward). Span and margin use the current units and modeling scale. Rotated or reflected local axes apply to the entire layout.

Align one item or a group

Use --start, --end, or --align center to position a single item within the space between the margins. No gap or explicit item width is needed:

part door:
    rect 10 20

layout --span 100 --count 1 --margin 10 --end:
    place door

The door starts at x = 80, leaving the 10 mm right margin. Center is the default for single items.

For multiple items, specify a fixed --gap to align the entire group. The group's size is the sum of its item sizes plus (count - 1) * gap. Alignment positions the free space before and after that group within the margins:

part door:
    rect 10 20

layout --span 100 --count 3 --margin 10 --gap 5 --align center:
    place door

The group is 40 mm wide and its doors start at x = 30, 45, and 60. --start would start them at 10, 25, and 40; --end at 50, 65, and 80. --gap accepts a nonnegative expression in the current authored units, including zero for edge-to-edge placement. Fixed-gap groups default to center alignment. Margins are the minimum clearance; alignment can leave additional space at either end. Specifying a gap does not resize the items or reduce the gap to make the group fit.

Align items in equal slots

--slots divides the span between the margins into count equal slots and places one copy of the body inside each slot. The slot size is computed as (span - 2 * margin) / count; no explicit slot width is needed:

part door:
    rect 10 20

layout --span 110 --count 3 --margin 10 --slots --align center:
    place door

Each slot is 30 mm wide. The doors start at x = 20, 50, and 80. With --start they would start at 10, 40, and 70; with --end at 30, 60, and 90. Center is the default for slots. Different-size items are aligned individually within their own slots, and each item must fit its slot even if the total item size fits the overall span. A slot can contain a whole motif, including multiple shapes or placements, as the body is measured as one item.

When --start, --end, or --align center is supplied without --gap, equal slots are assumed. For example, layout --span 110 --count 3 --margin 10 --start: places the doors at x = 10, 40, and 70, just as explicit --slots --start does. Specifying --gap selects group alignment instead, including --gap 0. Explicit --slots remains available, with center alignment by default. --slots and --gap cannot be combined. Omitting --start, --end, --align, --slots, --gap, --include, and --exclude preserves the default equal-gap distribution.

Alignment follows the local layout axis. --vertical --start places items toward the start of the local y span, --end toward its end, and --align center midway. These names are relative to the local coordinates, including when the local plane is rotated or reflected. --start and --end take no values; choose only one alignment option per layout.

The older --align left, --align right, --align start, and --align end forms are deprecated. They preserve their geometry and produce a warning; replace them with --start or --end. --align center remains supported.

Selecting positions

--include places copies only at the listed positions. --exclude leaves the listed positions empty. Positions are 1-based, and filtering preserves the full count and the original positions without moving the remaining items:

part door:
    rect 10 20

layout --span 120 --count 6 --include 3 4:
    place door

This divides the span into six 20 mm slots, then places doors only in slots 3 and 4, at x = 45 and 65. Either selector implies equal slots when --gap is absent, with center alignment by default. --start or --end can still position each selected copy within its slot.

Without --include, every position is eligible. If both selectors are present, exclusion wins: --include 2 3 4 --exclude 3 places copies in slots 2 and 4. Duplicate positions select a slot only once, and copies are processed in slot order regardless of the order of arguments. Each selector accepts one or more numeric expressions, such as --include FIRST (FIRST + 1); every value must be an integer from 1 through count. A selector cannot be repeated on the same header.

layout.position retains the original slot number. In slot mode, unselected bodies are not evaluated; only selected copies must fit their slots. Omitted copies produce no geometry, holes, patterns, labels, debug points, editable shapes, or named points. An empty selection draws nothing and leaves the cursor and heading unchanged, while still requiring valid layout options and an indented body.

With an explicit --gap, selection preserves each omitted copy's width and its original gaps. The whole group remains aligned as if all copies were present. Its bodies are evaluated to measure that reserved space, and the full group must fit, even though only selected copies are placed. An empty selection skips body evaluation in either mode.

Scope and fit errors

The layout starts at the current cursor and restores the cursor and heading afterward. Use zero before the layout to anchor it at the current origin. Each copy evaluates in a fresh local frame with the same starting heading; zero within the body resets to that copy's local origin. Assignments within the body are local to the copy. A body containing several shapes or placements is measured and repeated as one item. Layouts can nest and can be used inside parts, groups, coordinate blocks, and repeats. Like part placement, layout is not supported inside an indented fabrication operation; put the operation inside the layout instead.

layout.position is a read-only, 1-based iteration number. It is available throughout the body, including placed parts and conditions. An inner layout has its own position; the outer position is restored after the inner layout. Copies with different sizes get the same gap between their logical edges:

layout --span 100 --count 3 --margin 10:
    rect (layout.position * 10) 4

These items have widths 10, 20, and 30, with a gap of 10. A single item is centered between the margins unless another alignment is specified. Count must be an integer from 1 to 10000, span must be positive, and margin must be nonnegative. Every copy must produce geometry. An exact fit with zero gaps is valid; items that cannot fit produce an error rather than overlap or resize automatically.

Fit errors report the evaluated span, count, margins, available space, each item's logical dimensions and local bounds, total occupied space, minimum required span, and shortfall. Distributed layouts show the negative gap that would be needed to fit. Fixed-gap errors include the specified gaps and the maximum gap that fits, or explain that the items alone are too large. Slot errors identify the oversized copies, their slot overflow, and the minimum span needed for equal slots large enough to hold every item. Dimensions use authored units; scaled designs also show the physical output shortfall in millimetres. Identical items share one dimensions summary; for more than 20 different items, the list is abbreviated but totals include all items. These details do not change the fit check or the design.

Walks

walk applies a cursor step after every drawing command, placed part, or group in its body, including items inside sequential repeat blocks. 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, or tab 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.

Sequential repeats inside a walk apply its step after every repeated item:

component front_wall:
    walk 0 FLOOR_HEIGHT:
        place floor_1 --doors 2
        repeat N_FLOORS - 1:
            place floor_1

This places the ground floor at the current origin, then stacks the remaining floors at intervals of FLOOR_HEIGHT. The repeat block adds no extra step. Nested sequential repeats are supported, and repeat.position works as usual. Walk --debug numbering continues across iterations. Repeat counts must be positive integers; this example assumes N_FLOORS is at least two.

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, arc, arcbox, bezier, close, rect, ellipse, 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 its statements 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, label, and group, coordinate, walk, conditional, or sequential repeat blocks. At the top level or inside a part, they may also contain place and array repeats. After the block, result.* describes the final command of the final iteration.

Because placing a part does not move the cursor, use move to advance between placements:

repeat 4:
    place window
    move 16.5 0

This places four windows spaced 16.5 units apart and leaves the cursor 66 units past its starting position. Scoped coordinate planes also apply to repeated placements.

Inside a sequential repeat, these read-only values are available in expressions, conditions, and placed parts:

Value Meaning
repeat.position Current iteration number, starting at 1
repeat.first 1 on the first iteration, otherwise 0
repeat.last 1 on the last iteration, otherwise 0

Use the flags directly in conditions. For example, omit the gap after the last archway in a flow:

flow:
    repeat N_WIDE:
        place archway
        if not repeat.last:
            place arch_gap

A repeat with one iteration has both repeat.first and repeat.last set to 1. A zero-count repeat skips its body. Nested sequential repeats temporarily replace all three values, then restore the outer values when they finish. The values are unavailable after the outermost repeat finishes. Array repeats duplicate a single prototype and do not supply their own repeat values; inside a sequential repeat, they inherit its values.

A sequential repeat can also 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 non-negative integer no greater than 10,000. A count of zero skips the indented body without drawing, placing parts, or changing the cursor or result.*. This also applies inside flow: and walk: blocks, so repeat N_FLOORS - 1: does nothing when N_FLOORS is 1. 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.

Conditional drawing

if condition: executes an indented branch when its condition is true. An optional else: at the same indentation executes when it is false:

part floor:
    parameters:
        doors none

    repeat 4:
        if in(repeat.position, doors):
            place door
        else:
            place window
        move 16.5 0

place floor --doors 1 4

With door and window parts defined, this places door, window, window, door. Omitting --doors, or passing --doors none, places four windows. The standalone conditional window example includes the part definitions, crossbars, and cornices.

Conditions accept true, false, none, numbers, part parameters, numeric comparisons (==, !=, <, <=, >, >=), and in(value, selection). Membership accepts an empty selection (none), a single number, or a numeric selection parameter. Arithmetic and numeric functions work inside conditions. Use not, and, and or to combine conditions; and binds more tightly than or, and both skip evaluation of their right operand when the left operand determines the result. In conditions, false, none, zero, empty text, and empty selections are false. Nonzero numbers (including negative numbers), nonempty text, and nonempty selections are true. A selection containing only zeros is still true because it contains items. Text is supplied through part parameters. not, and, and or return booleans. Unknown variables and invalid arithmetic still raise errors unless short-circuit evaluation skips them.

The condition is evaluated when execution reaches it, including each iteration of a sequential repeat. Only the selected branch executes; skipped branches do not move the cursor, draw geometry, or place parts. Conditions may be nested and used wherever their selected statements are valid, including inside operations, parts, groups, coordinate blocks, and repeat bodies. else: must immediately follow its if block, apart from blank lines or comments.

Compact block syntax

A block header may be followed by its first statement on the same line. That statement can itself be a block header:

WINDOW_WIDTH = 20
if 1: zero 0 ( WINDOW_WIDTH / 2 ):
    rect 10 20

This is equivalent to:

WINDOW_WIDTH = 20
if 1:
    zero 0 ( WINDOW_WIDTH / 2 ):
        rect 10 20

When the compact line ends with a block's colon, the following indented lines belong to that last block. Dedenting to the compact line's indentation ends all blocks opened on that line. Existing scope rules still apply: for example, zero ...: restores the previous origin and cursor when its block ends.

A command without a trailing colon completes the inline body:

if true: rect 10 20
else: rect 5 6

Do not put an additional indented body below a completed inline command. Use the expanded form when you need more statements or access to an intermediate block's indentation. An else: aligned with a compact if line belongs to the outer if; put nested conditions on separate lines to give them their own else: branches.

The shorthand also works with other block headers, such as cut, group, repeat, part, and component. Colons inside quoted text and the ratio in scale 1:2 retain their existing meaning. Comments and backslash continuations work as usual.

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.

debug: drawing outlines

Wrap drawing statements in debug: to highlight their geometry in the 2D browser preview. A bright magenta outline with a white halo appears above material fills and patterns. Its screen width stays constant as you zoom. Use an indented block, or put the child statement on the same line:

debug:
    rect 30 20
    move 5 5
    circle 3

debug: rect 10 5

The compact form can wrap another block, including a patterned group inside a reusable part. For an existing pattern preset named corrugated_metal:

part steel:
    debug: group --pattern corrugated_metal:
        move 0 BRICK_HEIGHT
        line 0 STEEL_HEIGHT
        line TOTAL_WIDTH / 2 PEAK_HEIGHT
        line TOTAL_WIDTH / 2 -PEAK_HEIGHT
        line 0 -STEEL_HEIGHT
        close

Place the part normally to see its outlines. They follow placements, coordinate transforms, repeats, and component layouts. Only the wrapper's children are highlighted; nested debug: wrappers do not thicken the highlight. Outlines show the child geometry even when a later Boolean operation removes or covers it. They do not outline every generated pattern stroke.

The wrapper preserves its children's cursor, heading, paths, computed results, and fabrication behavior. Existing debug and debug label cursor markers continue to work independently. Debug outlines contribute to preview bounds, but not material or layout bounds. They are omitted from printing, saved thumbnails, SVG and PNG exports, and 3D. Debug statements are not supported inside measure: blocks.

Complete command reference

Syntax Effect
units mm\|cm\|m\|in\|ft Select authored units
medium value [value ...] Declare intended fabrication mediums (advisory only)
scale numerator:denominator Set modeling scale
scale "name" Select a named modeling scale or nominal figure scale
defaults: followed by thickness expression Set document-wide 3D thickness
defaults: followed by z expression Set the global starting depth of material
Drawing block with --z expression\|default Override starting depth locally, or select the global default
defaults tab: Set document-wide defaults for tab options
defaults pattern: Set document-wide surface-pattern appearance
name = expression Assign a numeric variable
if condition: / else: Execute one of two indented branches
in(value, selection) Test numeric selection membership in a condition
pass Do nothing without changing geometry, cursor, or drawing state
join source.wall to receiver.wall [--in value] [--left\|--right value \| --from-left\|--from-right distance] Place an independent rectangular gabled extension; repeat for multiple copies. --from-left\|--from-right measures an outside-edge gap in drawing units; --left\|--right shifts from centre, with percentages using centre-to-flush travel.
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
move dx dy: Temporarily set a local origin relative to the current cursor, restoring cursor and plane on exit
move --to name.point: Temporarily anchor a local origin at a saved drawing point, preserving axes and heading
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
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] [--thickness expression\|default] [--pattern name]: Run drawing commands with optional thickness and pattern settings, then restore cursor position and angle
band [name] --width expression [--align center\|left\|right] [--cap flat\|vertical] [--name identifier]: Turn straight or curved paths into a constant-width cuttable band; a name exposes the whole path and width to pattern follow
add [--thickness expression\|default] [--pattern name]: Merge child cut shapes; convert interior edges into scores; optionally set thickness and pattern
subtract [--thickness expression\|default] [--pattern name]: Remove subsequent closed shapes from the first shape; optionally set thickness and pattern
clip [--z expression]: Use the first closed shape as a mask and trim each later child independently; omit the mask
group [label] scored: Convert duplicate cut lines in the group into one shared score
background "name" [--mode kind] [--at x y] [--clip kind] [--tile] Place a library image by name directly inside a group
image alias: Declare a private library image by name or ID, with fallback pixel size, an optional pixel crop, and width or height
background image_alias [--mode kind] [--at x y] [--clip kind] [--tile] Use a declared image as a group background
reference image_alias [--fade percentage] Show a sized image at the document or component origin with guide bounds; fade preview material fill by 20% by default
pattern kind: Tile vector surface markings over a part, cut, or group’s closed shapes
pattern kind --name name: Define a reusable pattern preset without drawing
pattern --use name: Apply a pattern preset with optional local setting overrides
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 [--angle initial_tangent_angle] Draw a circular arc with an optional initial tangent and update the cursor angle
close Close the active path
zero [dx dy] Reset the cursor, optionally moving from the origin afterward
zero --to drawing.point [--angle expression]: Use a named point as a temporary local origin, optionally rotating its axes
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
ellipse width height Draw a closed oval fitting a box anchored at the cursor
space width height Reserve an invisible rectangle for layout; advance along the selected axis inside flow
triangle base height [--apex offset] [--rounded radius] Draw a triangle with an optional apex offset and rounded corners at the cursor
arch span rise --band width Draw a closed circular arch band from opening span and rise
arcbox span rise Draw an open circular arc from endpoint span and signed crown rise
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 [--pattern preset]: / parameters: Define reusable local geometry with parameters and an optional pattern preset
place name [arguments...] [--name value...] [--notabs] Place a part, optionally converting its tabs to lines
flow [--horizontal\|--vertical] [--gap expression] [--thickness expression\|default]: Arrange children along positive local x (default) or y, with optional gaps between them
layout --span expression [--count expression] [--margin expression] [--horizontal\|--vertical] [--gap expression\|--slots] [--start\|--end\|--align center] [--include position ...] [--exclude position ...] [--thickness expression\|default]: Distribute copies (count defaults to 1), align groups or slots, and select positions to place
distribute --span expression [--margin expression] [--horizontal\|--vertical] [--thickness expression\|default]: Arrange children across a span with equal gaps between their logical edges
center --width expression [--height expression]: or center --height expression: Center the whole composition within a width, height, or both, then restore the cursor and coordinate plane
box width height [--name identifier]: Declare hard layout bounds without clipping geometry; read dimensions as box.width/box.height or through the optional scoped name
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
debug: Outline child geometry above material and patterns in the 2D 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.

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.

Clones keep a record of the original design's name, owner, and revision, even if the original is later deleted. This also applies to system designs.