Skip to content

Alignment requirements

This page says what LayerForge should do so that cut layers can be stacked in exactly one way. None of it is built yet. The Requirements page lists what the tool does today. Its Known gaps cite the IDs below (TR-1 and so on) to show which target closes which gap.

The same target is written as a formal specification in specs/alignment.allium. The rule names there match the IDs here.

What the owner decided

These facts drive every requirement below. They came from the project owner on 2026-09-24 and 2026-09-25.

  • Layers are cut from flat sheet, for example with a laser. A person stacks the cut layers to rebuild the solid.
  • A reference mark is a hole cut through the sheet. Holes let a person align opaque layers by eye.
  • Every layer must be alignable to the layer below it and the layer above it.
  • There must be exactly one way to rebuild the model. Nothing may be ambiguous.
  • Each layer carries its number. The person cutting chooses to engrave it (marked on one side only) or to cut it through (readable only from the correct side).
  • Units are configurable and default to millimetres.
  • A later version may set aside some holes as dowel holes. They carry a size and shape of their own and are separate from the alignment marks.
  • The tool must not suit one machine only. It should work with hobby and professional lasers, CNC routers and hand methods (cutting, engraving, drilling). The owner's own machine is an xTool bought around 2022 (model to confirm). It is one test machine, not the design target.

Terms

Term Meaning
Layer One slice of the model, cut from one sheet. The SVG is its view from above (the +z side).
Piece One polygon of a layer, with its holes. A layer can have several pieces.
Adjacent pieces A piece in layer i and a piece in layer i+1 whose regions overlap by at least checks.min_overlap_area. The default is 0, so any overlap counts.
Reference mark A hole cut through the layer that helps align it. Also called an alignment hole or registration hole. The code keeps the name "reference mark".
Shared mark A reference mark that is a hole in both of two adjacent pieces.
Footprint The outline of a mark's hole, not only its centre.
Number The layer number printed on each piece.
Dowel hole A hole sized for a dowel. Planned for a later version, see TR-15.
Unit The length unit set by --units. The default is mm.
Setting A number or choice the person can change, with a default. Each has a config-file key such as marks.size. See TR-16.

How alignment is judged

Three jobs are split between the number and the marks.

Job Done by How
Face up or flipped The number Text reads correctly from one side only.
Rotation The shared marks No rotation other than none maps the marks, with their shapes and directions, onto themselves.
Position and precision The shared marks The marks pin the position. Their distance apart sets how well they fix rotation.

Each case below is a check the tool can run.

Shared marks on the piece pair Rotation fixed? Flip fixed by
One mark with a direction (triangle, arrow) Yes The number
One circle or one square No. A circle has no direction and a square repeats every 90°. Not enough. Rejected.
Two marks of different shapes Yes. A 180° swap would exchange the shapes. The number
Two identical circles or squares No. A 180° swap maps them onto themselves. Not enough. Rejected.
Three or more marks with all side lengths different Yes The marks themselves

Requirements

All rows are planned. "Closes" names the known gaps the row addresses, with the issue number in brackets.

Alignment

ID Requirement Closes
TR-1 The layers, each placed with its number readable from above, can be assembled in exactly one way. Goal
TR-2 Take any two adjacent pieces (see Terms). At least one shared mark exists, and no rotation other than none maps the set of shared marks onto itself. Two marks are the same only if shape, size and angle match. Every piece meets this with each piece it overlaps in the layer above and in the layer below. The lowest and highest layers have one neighbour. Status: the pairs of overlapping pieces are computed by adjacent_pairs (#89), and the rotations that map a set of marks onto itself by rotation_symmetry (#90, Python API, about the centroid of the mark centres, within a tolerance that is a length). check_alignment calls both for every pair before any file is written (#92). test_alignment_end_to_end proves it from the written files of a cube, a cylinder, a tube and an L-shaped part (#60). G-4 (#60)
TR-3 The number fixes whether a layer is face up. An engraved number shows on one side only. A cut number reads correctly from one side only. The rule for the person assembling is: place each layer with its number readable from above. The marks do not have to break mirror symmetry. G-4 (#60)
TR-4 When two or more marks are shared, the two farthest apart are at least the minimum baseline apart. A shorter baseline is a warning. A single shared mark has no baseline. Its own size sets the precision. The baseline is marks.min_baseline_factor × mark size. The default factor is 4 (proposed). Status: not built. A pair shares at most one mark today (plan_marks, #63), so there is no baseline to check. G-4 (#60)

Reference marks

ID Requirement Closes
TR-5 A reference mark is a hole. Its footprint is a closed outline that lies wholly inside the piece. The centre is at least --mark-min-distance from every outline of the piece, holes included. The footprints of two holes are at least marks.min_web_ratio × layer height apart, and so is a footprint from every outline of the piece (the material between cuts). The default ratio is 0.5. No footprint overlaps the number. The footprint is checked, not only the centre. Status: built in #85. The number clause is built in #75: the number is placed clear of every footprint, but nothing yet moves a mark for the number (TR-12, #92). The command chooses the point of a new mark with a disc that holds the outline of every available shape at any angle (radius: half the size, and for the circle a little more, see TR-7), and checks the exact footprint after the marks are chosen. G-21 (#85, done except the number)
TR-6 The default mark size is the larger of marks.min_hole_ratio × layer height and marks.min_hole_kerf_factor × kerf. The layer height is the sheet thickness. The defaults are 1 and 1.5, so with a 3 mm sheet and 0.3 mm kerf the size is 3 mm. --mark-size sets a size. A size below that minimum is a warning, not an error, because the person knows the machine. The size does not depend on the model's size. The default --mark-min-distance is the mark size. Status: built in #62 and #76. The size, the minimum distance and the tolerance are worked out once per run from the layer height, and a 10 mm cube gets marks with the default options. G-6 (#62, done), G-19 (#76, done)
TR-7 Every shape has a closed outline (a circle or a polygon), anchored at its centre. Its size is the diameter of the smallest circle around the anchor that holds the outline. Angle 0 points along +x and angles turn counter-clockwise. Angles are radians in the package and degrees on the command line. Each shape states its rotational symmetry: circle unlimited, square 4, triangle 1 (isosceles), arrow 1 (a closed arrow outline). Status: built in #84. The outline of a circle is a polygon of 64 vertices just outside the true circle, so a check never passes a circle that crosses an edge. The checks use this polygon and not the exact circle that the drawing cuts (decided in #157): it reaches 0.12% past the true radius (5.00603 for 5 at size 10, measured), which is 0.0018 at size 3 and far below a kerf. The proportions are fixed: the triangle has a 40° apex, and the arrow has a head 1.2 times and a shaft 0.5 times the radius wide. G-20 (#84, done)
TR-8 The tool chooses shapes so that TR-2 holds. A piece with room for one mark gets a shape with a direction. Two marks on a piece differ in shape, or use shapes with a direction. --available-shapes limits which shapes it may use. It no longer sets an order. Shape, size and angle are the same wherever a mark appears. Status: built in #61 for one mark per piece. choose_shape takes the shape with the least symmetry order (the triangle at the defaults, then the larger outline, then the name), and every mark of a run has one angle, so one mark fixes rotation. Two marks on one piece are not kept apart: #63 can put two marks on a piece, and nothing makes them differ. Measured on main at 9deb951 (layer height 3, the defaults): the sheared cylinder of #107 has two triangles on one piece in slice 11, and two legs that merge into one top (plan_marks on boxes) leave the top with two triangles. check_alignment passes both, because it looks only at the shared marks. The rule's second sentence is also too weak: two triangles both have a direction, and a shift can still line up the wrong two holes. A rewrite and the build are drafted in docs/superpowers/specs/2026-10-09-distinct-marks-and-mark-cap-design.md (a draft the owner has not reviewed); the shapes it needs are #230. Tracked in #213 item 1. With no shape that has a direction in the list, every pair fails the check of TR-12 (#92). G-5 (#61, done); G-32 (#213)
TR-9 Marks are local to pairs of layers. A mark stays in use while it remains valid in the next layer and is retired when it leaves the outline or clearance. Marks for two adjacent layers are chosen inside the overlap of their outlines, shrunk by the clearance of TR-5, so each shared mark is a hole in both. Two holes in different layers are the same mark when their centre, shape, size and angle are identical (TR-10). In a stack of three or more layers, no alignment mark is a hole in every layer. Dowel holes are the exception. Status: the reuse-and-retirement half is built and measured in #63. adjacent_pairs (#89) gives the overlap of each pair and the overlap shrunk by a clearance. plan_marks chooses a mark per pairing: it reuses a candidate carried from the pairing right before it, on the same piece, when the candidate still fits the new shrunk overlap, and otherwise samples a fresh point inside it, first fit. A mark is retired — simply not carried further — the moment it stops fitting; nothing revives one from farther back. Measured on the sheared cylinder of #107 (radius 20, height 60, shear 0.5 per z, layer height 3, tolerance=25, min_distance=10): 15 of its 20 slices get a mark, and the other 5 (slices 8 to 12) fail the check of TR-12 instead of staying silently unmarked, because their shrunk region lies entirely within tolerance of a mark retired at an earlier boundary in the same run of unmarked boundaries. The "no alignment mark is a hole in every layer" clause is not built: nothing forces a mark to retire when the geometry never stops fitting it, so a straight, undrifting shape gets one mark reused at every boundary. Measured: a 20 mm cube sliced into 7 layers, and a straight cylinder (radius 30, height 30, no shear) sliced into 10 layers, each get exactly one mark spanning every single slice. Known, accepted gap; real enforcement is tracked in #215, not this issue. The owner chose a soft cap on 2026-10-09: a setting marks.max_layers (default 4, proposed), a fresh mark of another shape at the cap, and a warning when none fits. It is drafted in docs/superpowers/specs/2026-10-09-distinct-marks-and-mark-cap-design.md and not built. G-7 (#63, done); every-layer clause tracked in #215
TR-10 A shared mark has identical coordinates in every layer that holds it. A mark that still fits the next pair is reused with its stored coordinates. A fresh point is refused when it lies within the snapping radius of a mark in play for its pairing, so it cannot be taken for that mark. --mark-tolerance (marks.tolerance) is that radius. The default is 0.1 × mark size (proposed; built in #62). Status: built in #63 and #210. The marks in play are the candidates carried from the pairing before, including those retired at this pair, the marks already placed on either piece by a different pairing, and, across a boundary that places no mark, the positions that were in play before it. The first wording of this row (a new point near a stored mark takes the stored coordinates, and the nearer of two wins) assumed a registry of the whole stack, which #63 removed. G-15 (#72), G-22 (#82)

Number

ID Requirement Closes
TR-11 Each piece carries its layer number, not the word "Slice". The number is the slice index, which starts at 0, as in the file name slice_000.svg. The number is drawn filled, so it engraves. It reads left to right along +x when the layer is viewed from above. It is drawn --number-height tall (number.height). The default is 5 mm (proposed). Its footprint lies wholly inside the piece, clear of the outline, holes and marks. The width is estimated conservatively as number.width_factor × height per character. The default factor is 0.6 (proposed). The font is bold sans-serif, written as font-family="sans-serif" and font-weight="bold". The centre is where the box is farthest from every cut. If no place fits, the number is drawn at the middle of the piece and one warning names the slice. Status: built in #75, except that the warning stays a warning (TR-12, #92, owner's decision of 2026-10-09), the font is not a setting, and the number is always engraved (no cut number). The box is width_factor × number.height × the digits, by number.height, and it keeps the kerf clear of every cut. G-18 (#75, done)

Checking and failure

ID Requirement Closes
TR-12 After slicing and before writing any file, the tool checks TR-2 for every pair of adjacent pieces. If one fails, it writes nothing, prints to stderr the slices and pieces of the pair and the reason, and exits with code 1. --allow-unaligned (checks.allow_unaligned) turns these errors into warnings, writes the files and exits with 0. A number that does not fit (TR-3, TR-11) and a number that would overprint a mark (TR-5) are warnings, not errors (owner's decision, 2026-10-09). A short baseline (TR-4) is always a warning. Status: built in #92. check_alignment (models/slicing/alignment_check.py) finds the marks two slices share (the same mark in both, with its centre inside both pieces) and fails a pair that shares none (no_shared_mark) or whose shared marks a turn maps onto themselves (rotation_not_fixed). A model of one layer, a piece that overlaps nothing, and two pieces that overlap by less than checks.min_overlap_area (TR-9) have no pair and pass without a message. G-4 (#60)

Units and output

ID Requirement Closes
TR-13 --units sets the unit of the mesh and of every length option: layer height, target height, mark size, distances, tolerance and number height. The choices are mm, cm and in. The default is mm. Every SVG has the same width and height, given with that unit, and a viewBox in the same numbers. --scale-factor still rescales the mesh. Status: built in #74 (--units, the units key, and width and height with the unit). No number that you give is converted. Built in #62 for the defaults that are stated in mm: the layer height (3 mm) and the kerf (0.3 mm) are stated in the unit of the run when nobody set them. The hairline width (0.01 mm) is converted too (#83). The number height (5 mm) is converted too (#75). G-17 (#74, done)
TR-14 Cut geometry (piece outlines and holes) is drawn in one colour, default red, with a stroke of output.hairline_width and no fill. The default width is a hairline of 0.01 mm, converted to the chosen unit. The number is drawn in black, filled, with no stroke. --cut-color and --engrave-color (output.cut_color, output.engrave_color) change the colours. --mark-color and the per-shape default colours are removed. The root stroke-width and font-size of the current output are removed. Colour groups objects. It does not set the operation in every program (TR-17). Status: built in #83 and #75. #83 (done)

Dowel holes (later)

ID Requirement Closes
TR-15 A later version may mark some holes as dowel holes, for a dowel or pin that holds the stack in line. A dowel hole is separate from the alignment marks and is not counted in TR-2, so dowels stay optional hardware. Proposed design, waiting for the owner's review (#93). Nothing is built (#142). (1) Shape: a circle, or a slot with fully round ends. No corners, because a CNC bit leaves an inside corner with the radius of the bit, and a hand drill makes only round holes. (2) Size: dowel.diameter is the measured diameter of the dowel, in --units. Any positive number is allowed, so there is no list of sizes. Measure the dowel, because a wooden dowel varies by about 0.13 mm (0.005 in). (3) Fit: dowel.fit is slip or press, and dowel.clearance (hole size minus dowel size) overrides the preset. (4) Kerf: a laser hole comes out larger than its path by the kerf, so the path is drawn at the wanted hole size minus kerf. kerf = 0 draws the path at the wanted size, for CNC software that offsets the tool itself and for hand work. (5) Marking: dowel holes use the cut colour and hairline of TR-14, so the person sets no extra operation, and carry class="dowel" in the SVG. dowel.color may set another colour. With dowel.mode = centre the tool draws a small cross in the engrave colour at each centre instead of a hole, for a hand drill or a CNC drilling operation. (6) Checks: a dowel hole below the minimum hole size of TR-6 is a warning. (7) Position and count are open. Two dowels fix the position and rotation of a stack, and one fixes position only. Each dowel hole must lie inside the overlap of every layer its dowel passes through. Later

Machine and process neutrality

ID Requirement Closes
TR-17 The output does not assume one machine or one program. (1) Geometry is exact: closed shapes at true size in physical units (TR-13). No size depends on a stroke width. (2) Colour never carries the operation alone, because a program may ignore it. xTool Creative Space groups objects by colour and asks the person to set the operation for each group. The colours of TR-14 are default groups. (3) Every number that depends on the machine or the material is a setting (TR-16). kerf = 0 gives true-size paths for CNC and hand work. (4) A check that depends on the machine is a warning, as in TR-6. (5) The tool writes a calibration sheet (#141), so a person measures kerf, the smallest clean hole, dowel fit and number height on their own machine and material. (6) SVG is the only output format. Another format, for example DXF, is added only when a named tool cannot read SVG. (7) The number is <text> and depends on the fonts of the receiving program. Converting it to outlines (#94) makes it portable. G-23 (#83), TR-15 (#93), #96, #141

How common tools read the output, which TR-17 rests on:

Tool How it picks the operation What the output needs
xTool Creative Space Colour makes a layer. The layer does not set parameters, so the person sets the operation for each layer. Consistent colours. The guide (#95) says to set the operation per layer.
LightBurn Colour makes a layer. Stroke width is ignored. Consistent colours.
Epilog, Trotec and Universal drivers An RGB red stroke of 0.001 in (0.025 mm) or thinner is a vector cut. A thicker stroke is engraved. A red hairline of 0.01 mm or thinner, no fill (TR-14).
CNC software The person selects geometry and assigns toolpaths. The software offsets by the bit radius. Closed paths at true size, kerf = 0, round holes and rounded slot ends.
Hand tools None. A print at 1:1 scale (TR-13). Centre crosses for drilling (TR-15).

Settings

ID Requirement Closes
TR-16 Every number in TR-2 to TR-15 that depends on the machine, the material or judgment is a setting. Each has a default and a config-file key (see the table). The common ones also have a command-line option. A setting is taken from the command line first, then from the config file, then from its default. The config file is TOML. Its path is --config, or layerforge.toml in the current directory if that file exists. Lengths are in --units. Unknown keys, wrong types and out-of-range values stop the run before slicing, with exit code 2 and a message that names the file and the key. Status: #87 built the mechanism and the keys layer_height, marks.size, marks.tolerance, marks.min_distance, marks.shapes and marks.angle. #74 added units. #85 added marks.min_web_ratio, which has no command-line option. #75 added number.height (--number-height) and number.width_factor (no option). #83 added output.cut_color, output.engrave_color (both with an option) and output.hairline_width (none). #62 added kerf (--kerf) and marks.min_hole_ratio and marks.min_hole_kerf_factor (no option), and made marks.size, marks.tolerance and marks.min_distance derived when they are not set. #63 added checks.min_overlap_area (no command-line option). Each later issue adds its own keys, and until then a key that is not built is an unknown key. G-24 (#87, done)
Key Option Default Basis Used by
units --units mm Convention TR-13
layer_height --layer-height 3.0 Existing default. It is the sheet thickness. TR-6
kerf --kerf 0.3 mm Proposed TR-6, TR-15
marks.size --mark-size larger of min_hole_ratio × layer height and min_hole_kerf_factor × kerf Sourced TR-6
marks.min_hole_ratio 1 Sourced (hole at least the thickness) TR-6
marks.min_hole_kerf_factor 1.5 Sourced TR-6
marks.min_distance --mark-min-distance mark size Sourced (centre one diameter from the edge) TR-5
marks.min_web_ratio 0.5 Sourced (material between cuts at least half the thickness) TR-5
marks.min_baseline_factor 4 Proposed TR-4
marks.tolerance --mark-tolerance 0.1 × mark size Proposed TR-10
marks.shapes --available-shapes circle, square, triangle, arrow Existing default TR-8
marks.angle --mark-angle (degrees) 0 Existing default TR-7
checks.min_overlap_area 0 Existing default (any overlap counts) TR-9
number.height --number-height 5 mm Proposed TR-11
number.width_factor 0.6 Proposed TR-11
number.font_family bold sans-serif Sourced TR-11. Not built: the font is fixed.
output.cut_color --cut-color red Convention TR-14
output.engrave_color --engrave-color black Convention TR-14
output.hairline_width 0.01 mm Sourced TR-14
checks.allow_unaligned --allow-unaligned false Decision TR-12
dowel.diameter --dowel-diameter none (dowels are off) Measured by the person TR-15
dowel.fit --dowel-fit slip Proposed TR-15
dowel.clearance from the fit: 0.2 mm for slip, 0 for press Proposed (from #93). Confirm with the fit ladder of #141. TR-15
dowel.mode hole (or centre) Decision TR-15
dowel.color the cut colour Decision TR-15

Defaults given in mm are converted to the chosen unit. A default that depends on another setting (for example the mark size) follows that setting.

Example layerforge.toml for a 3 mm sheet and a machine with a 0.25 mm kerf:

units = "mm"
layer_height = 3.0
kerf = 0.25

[marks]
size = 3.0
shapes = ["circle", "triangle", "square"]

[number]
height = 6.0

Command line changes

Option Change
--units New (#74). Default mm.
--config New (#87). Path to a TOML settings file (TR-16).
--kerf New (#62). Default 0.3 mm (proposed), stated in --units.
--mark-size New (#87). Sets the size. Without it the size follows the sheet thickness and the kerf (TR-6, built in #62).
--mark-min-distance Default changes from 10 to the mark size (built in #62). It measures centre to outline.
--mark-tolerance Now a snapping radius. Default 0.1 × mark size (proposed; built in #62).
--mark-angle Still degrees. 0 points along +x and turns counter-clockwise.
--available-shapes Limits the shapes. It no longer sets the order.
--number-height Built in #75. Default 5 mm (proposed), stated in --units.
--allow-unaligned New. Turns alignment errors into warnings. --no-allow-unaligned turns them back into errors for one run when the config file allows them (#222).
--cut-color, --engrave-color Built in #83. Replace --mark-color.

The project is at version 0.1.0, so removing --mark-color and changing defaults is acceptable.

Where the numbers come from

Search results from design guides gave these starting points. They are not measured on the owner's machine, and they vary with material and laser, so a test cut should confirm them.

Value Basis Status
Hole size at least the material thickness, or at least 1.5 × kerf Laser design guides Sourced
Centre at least one diameter from the edge Same Sourced
Material between two cuts at least half the thickness Same Sourced
CO₂ kerf about 0.25 to 0.5 mm SendCutSend Sourced
Hairline stroke for cuts, colour for the operation, text as outlines SVG preparation guide, text guide Sourced
Text under about 5 mm needs a bold sans font. Strokes of 0.3 mm or more. The Laser Co Sourced
STL has no units. Millimetres is the convention. UnitFYI Sourced
Through holes for dowels in stacked models Tulane makerspace Sourced
A laser hole comes out larger than its path. Offset hole paths inward by half the kerf on each side. MFG Workbench Sourced
XCS colours make layers, and layers do not set the operation. xTool support Sourced
LightBurn maps colour to a layer and ignores stroke width. LightBurn documentation Sourced (search result, not fetched)
Epilog, Trotec and Universal read an RGB red stroke of 0.001 in or thinner as a cut. Epilog file guide Sourced (search result, not fetched)
A wooden dowel diameter varies by 0.005 in (0.13 mm). Precision steel pins are held to hundredths of a millimetre. Chicago Dowel, DIN 6325 charts Sourced (the second is a search result, not fetched)
A CNC inside corner has at least the radius of the cutter. eMachineShop Sourced (search result, not fetched)
An SVG needs width and height in mm or in plus a matching viewBox, because Illustrator writes 72 px per inch and Inkscape reads 96. Inkscape wiki Sourced (search result, not fetched)
Dowel slip fit about 0.15 to 0.25 mm over the pin Search result recorded in #93 Proposed
Warnings do not change the exit code. Errors do. Command Line Interface Guidelines Sourced
Minimum baseline factor 4 Judgment Proposed
Snapping radius 0.1 × mark size Judgment Proposed
Number width factor 0.6 Judgment Proposed
Kerf 0.3 mm Inside the range in the row above Proposed
Default number height 5 Source says under about 5 mm needs a bold font Proposed
Angle 0 along +x, counter-clockwise Geometry convention. shapely rotates +x by +90° to +y. Checked

Assumptions to confirm

  • The default text is a bold sans font written as <text>. Converting the number to outlines comes later.
  • The default kerf of 0.3 mm is a proposal inside the sourced range of 0.25 to 0.5 mm.
  • Cut numbers (--number-mode cut) are not built. The number is always drawn to engrave.
  • The dowel design of TR-15 is a proposal for the owner's review (#93). Position and count are open, and the fit numbers need the fit ladder of #141.
  • The owner's machine is an xTool bought around 2022. xTool's site lists the X1 as upcoming, so the model is to confirm. The design does not depend on it.