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
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.
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.
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.
Ctrl+Enter or Command+Enter: renderCtrl+S or Command+S: saveCtrl+F or Command+F while the source editor is focused: open source find.
The Find button beside Render toggles the find bar and stays highlighted while
it is open.Ctrl+H or Command+Option+F in the source editor: open find with Replace
expanded. You can also toggle Replace in the find bar. Replace changes the
current match and advances; Replace all changes every match in one undo step.
Both respect Match case and treat replacement text literally; leave it empty
to delete matches. In the replacement field, Enter replaces the current
match and Ctrl+Enter or Command+Enter replaces all matches.Ctrl+Z or Command+Z in the source or geometry inspector: undo;
add Shift to redo (Ctrl+Y also redoes)Escape during a geometry drag: cancel the dragEnter or F3 in source find: select the next match; add Shift for the
previous matchEscape in source find: close find and return focus to the editorTab: insert spaces to the next multiple-of-four indent point; with a
selection, indent every selected lineShift+Tab, Ctrl+[, or Command+[: unindent selected linesCtrl+] or Command+]: indent selected linesCtrl+/ or Command+/: toggle comments on the current or selected linesCtrl+K, then Ctrl+C (or Command+K, then Command+C): comment linesCtrl+K, then Ctrl+U (or Command+K, then Command+U): uncomment linesEnter at the end of a line: preserve that line's indentationEscape, then Tab: leave the editor with keyboard focusUnless declared otherwise, a document starts with:
units mm)1:1(0, 0)0°, pointing rightz-units mm), independent of drawing scale#1d5c43) in the 3D previewTop-level geometry is allowed and is treated as an unnamed cut operation.
Using explicit fabrication blocks is recommended for real designs.
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.
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.
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 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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_lineUse 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.
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.
passDo 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.pointMove 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 dyWith 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.
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 dyDraw 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_angleAdd 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_angleSet 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.
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.
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 riseDraw 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.
closeClose 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
zeroReset 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 heightReserve 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 heightDraw 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 widthDraw 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.
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:
cut: material is cut through; red solid lines in the previewscore: fold or crease lines; blue dashed linesengrave: surface marks; dark solid linesguide: construction-only geometry; grey dashed linesGeometry 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.
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.
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.
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)
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.
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.
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.
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.
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.
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.
--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:.
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.
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.
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.
--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.
--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.
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.
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.
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.
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.
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.
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 outlinesWrap 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.
| 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.
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.
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.
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.