Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Boolean and transform operations

The Operations panel turns the current shape selection into edits: planar booleans, an offset, rotate and mirror, and align and distribute. It lives in reticle-app’s ops module. The heavy geometry is delegated to reticle-geometry (the same robust i_overlay engine the rest of the app uses); ops is the glue that maps selected scene shapes back to editable cell shapes, runs the operation, and records the result as one undo step.

What the selection points at

Selection is a set of indices into the flattened top-cell scene, the same list the canvas hit-tests. Only the top cell’s own shapes can be edited by index, and Document::flatten emits those first, so a scene index below the editable-shape count maps one-to-one onto a top-cell shape index. Selected indices at or above that count come from placed instances and arrays; there is no single shape in the top cell to rewrite, so the operations skip them.

Booleans and offset act on filled geometry, so they consider rectangles and polygons. A path is a stroked wire rather than a fill region, and turning it into a fill needs the render tessellator, so paths are skipped from boolean and offset input. Transforms (rotate, mirror, align, distribute) are coordinate maps and apply to every shape kind.

Booleans, per layer

Union, intersection, difference, and exclusive-or run through reticle_geometry::polygon_boolean. The selection is grouped by layer first: only shapes on the same layer combine, and each group’s result stays on that layer. This keeps a boolean from silently merging, say, metal-1 and metal-2 geometry. A layer group needs at least two fillable shapes to do anything.

Union, intersection, and exclusive-or fold pairwise across the group (they are associative). Difference subtracts every later shape from the first, matching the usual “A minus the rest” editor behavior. When the engine produces no geometry (an empty intersection, for instance) the inputs are left untouched rather than being deleted.

Offset, grow, and shrink

The offset control feeds reticle_geometry::offset, which grows for a positive DBU amount and shrinks for a negative one, with mitered corners. Each selected fillable shape is offset independently and its result replaces it on the same layer. A shrink that collapses a shape to nothing simply drops it. The offset runs on the float outline engine and rounds back to the grid, so results are on-grid but not guaranteed bit-exact for pathological input.

Rotate and mirror

Rotate takes a numeric angle in degrees and turns the selection about its combined bounding-box center, counter-clockwise. Rotation runs on a floating basis and rounds each vertex to the nearest DBU, so a multiple of 90 degrees is exact while an arbitrary angle is on-grid but not exactly reversible. A rotated rectangle is promoted to a polygon, since a non-orthogonal angle tilts it off the axes.

Mirror reflects the selection across its center, either about the vertical center line (left to right) or the horizontal one (top to bottom). An axis mirror is exact integer arithmetic (2 * center - coordinate), so mirroring twice about the same axis returns the original geometry.

Align and distribute

Align moves every selected shape to a shared edge or center of the selection’s combined bounding box: left, right, or horizontal center; top, bottom, or vertical center. A shape already in place does not move. Distribute needs at least three shapes: it fixes the two extreme shapes and respaces the ones between them so the edge-to-edge gaps along the chosen axis are equal.

The alignment and distribution math is pure and unit-tested against hand-computed offsets, independent of the UI.

One edit, one undo step

A single operation is usually several edits: a boolean removes each of its inputs and adds one result. Those must undo together. The frozen Edit vocabulary has no group variant, so the app layer’s History records how many underlying edits make up each logical step (History::apply_group) and steps undo and redo over a whole group at once. Removals within a group are ordered highest-index-first so that removing one input does not shift the index of another before it is removed. The result: a boolean over three rectangles is a single entry on the undo stack, and one undo restores all three.