From 2ca6e79f37b8e7ddf63380625d70652cbaa9473e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 16:54:58 +0000 Subject: [PATCH] 43-editor-cut-split: plan plane cut, split to bed, connectors, hole tool, text on face Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_0182QWEHEoWzZux4ViurCaKy --- openspec/ROADMAP.md | 2 +- .../43-editor-cut-split/.openspec.yaml | 2 + .../changes/43-editor-cut-split/design.md | 37 ++++++++++ .../changes/43-editor-cut-split/proposal.md | 31 +++++++++ .../specs/editor-cut-connectors/spec.md | 58 ++++++++++++++++ .../specs/editor-holes-text/spec.md | 67 +++++++++++++++++++ .../specs/editor-plane-cut/spec.md | 66 ++++++++++++++++++ openspec/changes/43-editor-cut-split/tasks.md | 43 ++++++++++++ 8 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 openspec/changes/43-editor-cut-split/.openspec.yaml create mode 100644 openspec/changes/43-editor-cut-split/design.md create mode 100644 openspec/changes/43-editor-cut-split/proposal.md create mode 100644 openspec/changes/43-editor-cut-split/specs/editor-cut-connectors/spec.md create mode 100644 openspec/changes/43-editor-cut-split/specs/editor-holes-text/spec.md create mode 100644 openspec/changes/43-editor-cut-split/specs/editor-plane-cut/spec.md create mode 100644 openspec/changes/43-editor-cut-split/tasks.md diff --git a/openspec/ROADMAP.md b/openspec/ROADMAP.md index 9162058..9089a2e 100644 --- a/openspec/ROADMAP.md +++ b/openspec/ROADMAP.md @@ -53,7 +53,7 @@ | 40 | `40-editor-sketch` | 39 | v1.11.0. Эскиз на плоскости: инструменты (прямоугольник с радиусом углов), Fillet/Chamfer 2D, ограничения и размеры, степени свободы, импорт SVG, Extrude/Revolve в тела (join/cut/intersect, фаска/скругление краёв); тело = эскиз + список операций, сетка производная (`EDITOR.md` 2a) | | 41 | `41-editor-op-stack` | 40 | v1.12.0. Неразрушающий стек операций (пересчёт от источников, `EDITOR.md` 2a), дерево/timeline, булевы, Mirror, Pattern, Hollow, Offset | | 42 | `42-editor-mesh-repair` | 41 | v1.13.0. Диагностика и ремонт STL, Decimate, Smooth, Lay on Face, измерения, анализ нависаний | -| 43 | `43-editor-cut-split` | 41, 42 | v1.14.0 (план). Plane Cut, Split с соединителями, отверстия под магниты/винты, текст на грани | +| 43 | `43-editor-cut-split` | 41, 42 | v1.14.0. Plane Cut, Split на стол с соединителями (шипы, штифты, магниты, ласточкин хвост), инструмент «Отверстие» (винты, вставки, магниты) и текст на грани | | 44 | `44-editor-face-tools` | 41 | v1.15.0 (план). Выбор граней/рёбер, Push/Pull, Fillet/Chamfer, эскиз на грани, Hole wizard | | 45 | `45-editor-project-default` | 43, 44 | v2.0.0 (план, major: `/` ведёт в редактор). Файл проекта `.pkit` (ZIP: scene.json + сетки), автосохранение, редактор — вход по умолчанию, генераторы как вставляемые объекты | diff --git a/openspec/changes/43-editor-cut-split/.openspec.yaml b/openspec/changes/43-editor-cut-split/.openspec.yaml new file mode 100644 index 0000000..97e3642 --- /dev/null +++ b/openspec/changes/43-editor-cut-split/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-27 diff --git a/openspec/changes/43-editor-cut-split/design.md b/openspec/changes/43-editor-cut-split/design.md new file mode 100644 index 0000000..bdce5c8 --- /dev/null +++ b/openspec/changes/43-editor-cut-split/design.md @@ -0,0 +1,37 @@ +# Design + +## Context + +The baseplate splitter (`geometry/split.ts`, `connectors.ts`) is grid-aware: cuts are between cells, connectors go into ribs. A general model has no ribs; connectors must be placed in the *section* of the cut. `41` supplies stack operations and booleans, `42` supplies planar region picking. Slicers (Cut tool with connectors), Meshmixer (Plane Cut, Separate Shells) and Fusion (Split Body, holes) are the references. + +## Goals / Non-Goals + +**Goals**: reliable cuts of closed solids, parts that print and assemble (clearances, depth limits), a hole tool with real fastener presets, editable and derived results. +**Non-Goals**: topology-aware smart cutting, threads, wrapping text. + +## Decisions + +1. **Derived parts.** `doc.cuts: CutGroup[]` where `CutGroup = {id, sourceObject, planes: Plane[] | grid {x[], y[], z[]}, connectors: ConnectorSet, bed?: {…}}`. Each resulting part is an ordinary object with `source: {kind:'cutPart', group, index}` (and its own transform = identity relative to the source frame, so the parts stay assembled in the scene by default, with an *Explode* slider like the baseplate's exploded view). The source object is hidden and listed under the group in the outliner; deleting the group restores the source. Editing the group recomputes all parts; parts may carry their own stack after the cut (`41`), applied to their derived solid. +2. **Cutting.** `cutSolid(solid, group)` splits with `Manifold.splitByPlane` (or `trimByPlane` for keep-one-side) for arbitrary planes; the grid variant is a sequence of axis splits ordered x, y, z. Requires a closed solid (guard from `41`). Both halves are closed by construction. Parts are labelled by column letter and row number as in `split.ts`; arbitrary single planes label A and B. +3. **Split to bed.** `autoCuts(size, usable)` per axis: fewest parts (`ceil(size / usable)`), cuts spread evenly, optionally shifted by a tolerance to align with section minima (sections with the smallest area are preferred within ±10 % of the evenly spaced position) — a cheap way to avoid cutting through large solid areas. The printer, bed and margin come from the export settings through `usableBed`. Parts bigger than the bed after the cut (diagonal parts of rotated objects are not rotated automatically) are marked `oversize` like the baseplate's. +4. **Section and placement.** The cut section is `CrossSection` from the solid sliced at the plane (`Manifold.slice`), expressed in plane coordinates. `placeConnectors(section, {kind, count, size, margin})` finds candidate positions on a grid whose cell is the connector pitch, keeps those whose clearance disc lies inside the section inset by `margin` (default connector diameter), chooses `count` positions by farthest-point sampling starting from the section's pole of inaccessibility (so pairs of pegs spread, singles go to the centre), and returns them in order. The user can drag, add and delete in a 2D preview of the section; manual positions are stored in plane coordinates on the cut definition so they survive edits of the source as long as they stay inside (otherwise warning `cut.connector.outside`, connector skipped). +5. **Connector solids.** Peg: cylinder of diameter *d* and length *l* on part A (union), socket of diameter *d + clearance* and depth *l + 0.3* on part B (difference), both chamfered at the tip. Pin hole: socket in both parts for a separate pin (diameter from the pin presets). Magnet pocket: in both parts, diameter *d + tolerance*, depth *h + tolerance* (polarity left to the user). Dovetail: a trapezoid profile (length, angle 15°, depth) extruded along the section polygon's chosen axis, key on A, socket on B grown by the clearance, depth limited by the material thickness. Depth for all is limited to the minimum of the configured depth and the available material behind the cut at that point minus 1.5 mm (ray distance through the solid along the normal); less than 2 mm available skips the connector with `cut.connector.thin`. Presets and clearances come from a shared `connectorSpec.ts` extracted from `connectors.ts` so the baseplate keeps its values. +6. **Collision checks.** After building, each connector solid is intersected with the part to verify it is fully inside (volume of connector ∩ part ≥ 99 % of the connector volume for the pocket, ≥ 99 % of the peg inside the opposite part's socket envelope); failures give `cut.connector.outside` or `cut.connector.overlap` with the connector index, shown in the preview in red. +7. **Hole tool.** Surface picking returns `(point, normal)` from the mesh raycaster (`42`); the hole op stores `{point, normal, up}` in object-local space plus parameters `{kind: 'simple'|'counterbore'|'countersink'|'magnet'|'insert', d, depth|through, cbD, cbDepth, csAngle, tolerance, preset?}`. The tool solid is a stack of cylinders/cones along the normal, starting slightly above the surface (+0.2 mm) and subtracted as a `boolean(subtract, generated tool)` in the evaluator (a generated tool is a solid, not a scene object). Presets: M2–M5 screw clearance (loose/normal), heads (DIN 912 and ISO 7380 sizes), countersink 90°, brass heat inserts M2–M5 (outer diameter and length table, documented from vendor ranges), magnets from the `06a` list plus common sizes (6×2, 8×3, 10×2); the table is data in `geometry/spec.ts` style, hand-written and cited in README. `through` extends along the normal to leave the solid (bbox diagonal). Offsets: numeric position fields move the point along the picked face's plane (face frame) relative to the object origin or to a picked edge. +8. **Text on a face.** `text` op: `{point, normal, up, text, font, size, spacing, align, mode: 'emboss'|'engrave', height|depth, rotation}`. The glyph outline polygons come from the rasteriser and `traceAlpha` outline code of Keychain, moved to a shared module `geometry/textShapes.ts`; polygons become a `CrossSection` (EvenOdd), extruded along the normal (emboss starts 0.05 mm inside the surface so the union is watertight; engrave starts 0.05 mm above), positioned by the frame. Only flat projection: on curved surfaces the text keeps its flat shape and the op reports `text.warning.curved` when the sampled surface deviates more than the text depth over the text extent. Fonts register via the existing lazy `FontFace` loaders. +9. **Viewport.** A cutting-plane widget (translucent quad with the gizmo of `39` attached, snaps to axes), cut section preview as a 2D inset panel, surface picking marker with the normal, ghosting of the hidden source. No new three.js dependency. +10. **History and persistence.** All edits go through `dispatch` with merging; `cuts` is a new optional field of the scene document (schema stays 1); normalisation drops connectors with missing groups. +11. **Export.** Derived parts export as ordinary parts with unique names `Name A1`; connectors that are loose pins (pin kind) are exported as separate small parts when "Include pins" is on (default on). + +## Risks / Trade-offs + +- **Connector placement quality** on odd sections (thin, branched) is heuristic; the preview and manual edit are the escape, and unplaceable connectors are reported rather than forced. +- **Dovetail on arbitrary sections** is only reliable on sections with a straight run of sufficient length; otherwise it is refused with the reason, and pegs are suggested. +- **Hole presets** are hand-written tables; wrong sizes would be annoying, so values are conservative, cited, and editable (custom values always possible). +- **Derived parts and stack ops** interplay: operations added to a part apply to the derived solid after the cut; editing the cut can invalidate them (they fail visibly, as everywhere in `41`). +- **Text quality** depends on font tracing; very small text or thin strokes warn using the same check as Keychain (`textResolution` logic). + +## Open Questions + +- Whether pegs should be part of A (union) or loose pins by default (assumed: integrated pegs by default, loose pins as an option). +- Whether to show the exploded gap slider in the editor viewport for all groups (assumed yes, per group). diff --git a/openspec/changes/43-editor-cut-split/proposal.md b/openspec/changes/43-editor-cut-split/proposal.md new file mode 100644 index 0000000..bd4e654 --- /dev/null +++ b/openspec/changes/43-editor-cut-split/proposal.md @@ -0,0 +1,31 @@ +# Proposal + +## Why + +Two of the most common reasons to open a model in an editor before printing are "it does not fit on my bed" and "I need a hole for a magnet/screw/insert here". Slicers have a Cut tool, Meshmixer has Plane Cut, and Gridfinity Baseplate already splits plates with connectors (`06`, `06a`) — but only for its own grid. This change brings the same capabilities to any model: cut with a plane, split to the printer bed, join the parts with pins or dovetails, drill fastener and magnet holes anywhere on a surface, and put text on a face. + +## What Changes + +- **Plane Cut.** A cutting plane defined by an axis and position, by the gizmo (any orientation), by a picked flat face, or by three points. Modes: keep both parts (as separate objects), keep one side, slice into N equal parts along an axis. The parts are *derived*: they keep a reference to the source object and the cut definition, so editing the plane or the source updates them (rule 2a). The cut faces are capped automatically (closed solids). +- **Split to bed.** One command computes the cuts needed so every part fits the printer bed of the export settings (bed minus margin), spread evenly, with a per-axis override and manual adjustment of each cut position; parts are labelled A1, B1… like the baseplate. +- **Connectors on cut faces.** Alignment pegs (cylindrical peg + socket with clearance), through-holes for filament or pins, magnet pockets, and dovetails, using the presets of `06a` (filament 1.75 → Ø1.9, pins 2/3 mm, magnets 3 × 2 and 2.8 × 0.75, dovetail clearance). Placement is automatic over the cut section (inset, even spacing, never closer than a margin to the outline) with click-to-move, add and remove in the preview; each connector is checked to lie inside both parts for its full depth. +- **Hole tool.** Click a surface to place a hole along the surface normal: simple, counterbore, countersink, magnet pocket, screw clearance and heat-insert pocket (M2–M5 and common magnet sizes from presets, with tolerance), blind or through, offsets typed numerically, arrays through the patterns of `41`. A hole is a stack operation with a stored position in object-local space. +- **Text on a face.** Emboss or engrave text at a picked surface point with font, size, spacing, depth/height and rotation, using the font list and rasteriser of Keychain (`19`) and the exact outline tracing, as a stack operation. +- **Plan checks.** Parts that still exceed the bed are marked; connectors that collide or leave a part produce warnings with keys, like the baseplate's `connector.*`. + +## Capabilities + +### New Capabilities +- `editor-plane-cut`: Plane Cut, split to bed, derived parts. +- `editor-cut-connectors`: pegs, pins, magnets, dovetails on cut faces, placement and checks. +- `editor-holes-text`: hole tool with presets and text on faces. + +### Modified Capabilities + +## Impact + +- New `src/geometry/cut/{plane,split,section,connectors,place}.ts` (reusing `PRESETS`/clearance constants of `connectors.ts` and `fasteners.ts` through a shared module), `src/geometry/features/ops/{hole,text}.ts`, doc field `cuts` (`CutGroup`), derived-part object source `{kind:'cutPart', group, index}`, `features/editor/{CutTool,SplitDialog,ConnectorEditor,HoleTool,TextTool}`, viewport support for the cutting plane widget and surface picking markers, strings, tests. +- Reuse: `Manifold.trimByPlane/splitByPlane`, `CrossSection` of the cut, `rasterizeText` and `textOutline` of Keychain (moved to shared modules if they are feature-private), `usableBed` and printers of `09`. +- Out of scope: cutting with curved or sketch-shaped surfaces (cut tool = a Boolean with a tool object, available from `41`), automatic best-cut search that avoids features, interlocking joints beyond pegs and dovetails, threads (real helical threads), text along curves or wrapped on curved surfaces (flat projection only), per-part colours, assembly animation. +- Version 1.14.0 (minor: a new capability). +- Depends on: `41-editor-op-stack` (stack operations, booleans, patterns, closed-solid guard) and `42-editor-mesh-repair` (planar face region for plane-from-face and surface picking). diff --git a/openspec/changes/43-editor-cut-split/specs/editor-cut-connectors/spec.md b/openspec/changes/43-editor-cut-split/specs/editor-cut-connectors/spec.md new file mode 100644 index 0000000..7d1ceae --- /dev/null +++ b/openspec/changes/43-editor-cut-split/specs/editor-cut-connectors/spec.md @@ -0,0 +1,58 @@ +# Spec Delta + +## Purpose +Defines connectors on cut faces: pegs, pin and magnet sockets, dovetails, their placement and checks. + +## ADDED Requirements + +### Requirement: Connector types +The Editor SHALL offer connectors on a cut face: integrated alignment pegs with sockets, holes for separate pins or filament, magnet pockets, and dovetails, with size, depth and clearance, using the presets of the baseplate connectors. + +#### Scenario: Peg +- **WHEN** a peg connector of 4 mm diameter and 8 mm length is added +- **THEN** part A has a peg of 4 mm and part B a socket larger by the clearance and slightly deeper + +#### Scenario: Filament hole +- **WHEN** the filament preset is chosen +- **THEN** both parts get a hole of 1.9 mm diameter and the default depth + +#### Scenario: Magnet pockets +- **WHEN** the magnet preset 6 × 2 is chosen +- **THEN** both parts get a pocket of the magnet size plus tolerance + +#### Scenario: Dovetail +- **WHEN** a dovetail is chosen on a section with a straight run of enough length +- **THEN** part A has the key and part B the socket with clearance, and the parts join by sliding + +### Requirement: Placement +Connectors SHALL be placed automatically over the cut section, evenly and away from the outline, and SHALL be movable, addable and removable in a section preview. + +#### Scenario: Two connectors +- **WHEN** count 2 is chosen on a rectangular section +- **THEN** the connectors are spread along the longer side with equal margins + +#### Scenario: Drag +- **WHEN** the user drags a connector in the preview +- **THEN** it moves and the check runs again for the new position + +#### Scenario: Thin section +- **WHEN** the section is too small for any connector +- **THEN** none is placed and the message says why + +### Requirement: Fit checks +Each connector SHALL be checked to lie fully inside both parts for its depth; problems SHALL be reported by key with the connector number and shown in red, and the connector SHALL be skipped. + +#### Scenario: Leaves the part +- **WHEN** a manually placed peg would leave the part +- **THEN** it is skipped with the "outside" message + +#### Scenario: Too little material +- **WHEN** less than 2 mm of material lies behind the cut at a connector +- **THEN** it is skipped with the "thin" message + +### Requirement: Loose pins +Connectors of the pin kind SHALL be exportable as separate small parts. + +#### Scenario: Include pins +- **WHEN** Include pins is on and the scene is exported +- **THEN** one pin part per pin connector is included and named diff --git a/openspec/changes/43-editor-cut-split/specs/editor-holes-text/spec.md b/openspec/changes/43-editor-cut-split/specs/editor-holes-text/spec.md new file mode 100644 index 0000000..9fcc461 --- /dev/null +++ b/openspec/changes/43-editor-cut-split/specs/editor-holes-text/spec.md @@ -0,0 +1,67 @@ +# Spec Delta + +## Purpose +Defines the hole tool with fastener presets and text on faces. + +## ADDED Requirements + +### Requirement: Hole tool +The Editor SHALL place a hole where the user clicks a surface, along the surface normal, with the types simple, counterbore, countersink, magnet pocket and heat-insert pocket, blind or through, using presets for common screws, inserts and magnets with a tolerance, and numeric position fields. + +#### Scenario: Place a screw hole +- **WHEN** the user clicks a top face and chooses M3 clearance, through +- **THEN** a hole of the M3 clearance diameter goes through the part along the face normal + +#### Scenario: Counterbore +- **WHEN** the counterbore type is chosen for an M3 socket head +- **THEN** the head recess has the preset diameter and depth over the clearance hole + +#### Scenario: Magnet pocket +- **WHEN** a 6 × 2 magnet pocket is placed +- **THEN** the pocket is the magnet size plus tolerance and 2 mm deep plus tolerance + +#### Scenario: Inclined face +- **WHEN** the face is tilted 30° +- **THEN** the hole axis follows the face normal + +#### Scenario: Type the position +- **WHEN** the user types offsets of 10 and 5 mm in the face plane +- **THEN** the hole moves to that position + +#### Scenario: Array +- **WHEN** the hole operation is combined with a linear pattern of `41` +- **THEN** the holes repeat with the given spacing + +### Requirement: Hole is an operation +A hole SHALL be an editable operation in the object's stack with its position stored in object space, so changing the source or reordering recomputes it. + +#### Scenario: Edit diameter +- **WHEN** the user changes the hole size later +- **THEN** the hole updates without losing its position + +#### Scenario: Missing material +- **WHEN** the stored position no longer lies on the surface +- **THEN** the operation still cuts where the tool volume meets material and reports a warning if nothing is cut + +### Requirement: Text on a face +The Editor SHALL emboss or engrave text at a picked surface point with font, size, spacing, alignment, rotation and depth or height, as an editable stack operation. + +#### Scenario: Engrave +- **WHEN** the user engraves "A1" 1 mm deep on a flat top face +- **THEN** the text is cut 1 mm into the surface + +#### Scenario: Emboss +- **WHEN** the user embosses text 0.8 mm high +- **THEN** the text stands 0.8 mm above the surface and is fused with the body + +#### Scenario: Curved surface +- **WHEN** the text extends over a surface that deviates more than the text depth +- **THEN** a warning says the text is projected flat + +#### Scenario: Cyrillic and Latin +- **WHEN** a font without the needed glyphs is chosen +- **THEN** the same fallback as in the keychain text applies and a notice is shown + +#### Scenario: Fine strokes +- **WHEN** the strokes are thinner than the nozzle allows +- **THEN** the thin-stroke warning is shown diff --git a/openspec/changes/43-editor-cut-split/specs/editor-plane-cut/spec.md b/openspec/changes/43-editor-cut-split/specs/editor-plane-cut/spec.md new file mode 100644 index 0000000..81b6f2e --- /dev/null +++ b/openspec/changes/43-editor-cut-split/specs/editor-plane-cut/spec.md @@ -0,0 +1,66 @@ +# Spec Delta + +## Purpose +Defines cutting a model with a plane, slicing, splitting to the printer bed and the derived parts. + +## ADDED Requirements + +### Requirement: Plane Cut +The Editor SHALL cut a closed solid with a plane defined by an axis and position, by the gizmo in any orientation, by a picked flat face, or by three points, producing capped parts. + +#### Scenario: Axis cut +- **WHEN** a 40 mm cube is cut at Z = 15 +- **THEN** two closed parts of 15 mm and 25 mm height are created and their volumes add up to the original + +#### Scenario: Keep one side +- **WHEN** the user chooses Keep upper side +- **THEN** only the part above the plane remains, with a flat closed cut face + +#### Scenario: Slice into N +- **WHEN** the user slices a 90 mm part into 3 along X +- **THEN** three parts of 30 mm are created + +#### Scenario: From a face +- **WHEN** the user picks a flat face as the cutting plane +- **THEN** the plane is placed on that face and can be offset by a typed distance + +#### Scenario: Open mesh +- **WHEN** the object is not a closed solid +- **THEN** the cut is refused with the reason and a Repair action + +### Requirement: Derived parts +Parts of a cut SHALL stay linked to the source object and the cut definition, so changing the plane, a connector or the source updates all parts; the source SHALL be hidden but kept and restorable. + +#### Scenario: Move the plane +- **WHEN** the user changes the cut position from 15 to 20 mm +- **THEN** both parts are recomputed and the connectors stay on the new cut face + +#### Scenario: Remove the cut +- **WHEN** the user deletes the cut group +- **THEN** the parts disappear and the source object is visible again + +#### Scenario: Reload +- **WHEN** the page is reloaded +- **THEN** the parts are rebuilt from the stored cut definition + +### Requirement: Split to bed +The Editor SHALL compute the cuts needed to fit every part within the printer bed minus margin from the export settings, spread them evenly, allow adjusting each cut, and label parts A1, B1… + +#### Scenario: Long part +- **WHEN** a 500 × 100 × 40 mm part is split for a 256 mm bed with a 5 mm margin +- **THEN** three parts are created along X, each not longer than 246 mm + +#### Scenario: Oversize left +- **WHEN** a part still exceeds the bed after the cuts (for example diagonally) +- **THEN** it is marked oversize with the same warning as in the export panel + +#### Scenario: Printer change +- **WHEN** the printer in the export settings is changed +- **THEN** the suggested cuts are recomputed, manual cut positions are kept + +### Requirement: Exploded view +Cut groups SHALL offer an exploded view with a gap slider for inspection that does not affect exports. + +#### Scenario: Explode +- **WHEN** the gap is set to 10 mm +- **THEN** the parts move apart in the view and exporting uses the assembled positions diff --git a/openspec/changes/43-editor-cut-split/tasks.md b/openspec/changes/43-editor-cut-split/tasks.md new file mode 100644 index 0000000..a9ed3c7 --- /dev/null +++ b/openspec/changes/43-editor-cut-split/tasks.md @@ -0,0 +1,43 @@ +# Tasks + +## 1. Spike + +- [ ] 1.1 Cut: verify `splitByPlane`, `trimByPlane` and `slice` in the installed `manifold-3d` on a bracket and an imported closed STL; check that both halves are closed and volumes add up, record timings; prototype pole-of-inaccessibility and farthest-point placement on three sections (rectangle, L, ring) and record the result in `design.md` Decision 4. +- [ ] 1.2 Reuse: list which of `rasterizeText`, `textOutline`, the font loaders and connector presets are feature-private; decide the shared module layout (Decision 5, 8) and note it in `design.md`. + +## 2. Cut geometry (React-free) + +- [ ] 2.1 `geometry/cut/plane.ts`: plane definitions (axis+position, normal+point, three points, from face frame), `cutSolid`, keep-one-side, slice into N; labels. Test with `expectValidSolid`: volumes add up, bboxes, closed caps, arbitrary 30° plane, open input refused. +- [ ] 2.2 `geometry/cut/split.ts`: `autoCuts` (fewest parts, even spread, small-section preference within ±10 %), usable bed from `usableBed`, grid split order, oversize flags. Test: 500 × 100 on a 256 bed, rotated block, manual cut kept after a printer change. +- [ ] 2.3 `geometry/connectorSpec.ts` extracted from `connectors.ts` (presets, clearances) without changing the baseplate: existing `connectors.test.ts`, `split.test.ts` and baseplate volume tests stay green. +- [ ] 2.4 `geometry/cut/section.ts` and `place.ts`: section polygons at a plane, inset, pole of inaccessibility, farthest-point placement, manual positions in plane coordinates. Test the placement on the three reference sections and thin sections. +- [ ] 2.5 `geometry/cut/connectors.ts`: peg/socket, pin holes, magnet pockets, dovetail solids, depth limited by the material behind the cut (ray distance), fit checks with `cut.connector.*` keys. Test: volumes of pegs and sockets, depth limit on a thin part, outside/overlap detection, dovetail joins (A key fits B socket with clearance, no overlap beyond it). + +## 3. Holes and text operations + +- [ ] 3.1 Hole data: `geometry/holeSpec.ts` with M2–M5 clearance, head and insert tables and magnet sizes (hand-written, cited in the README references). Test: table sanity (monotonic, clearance > thread). +- [ ] 3.2 `geometry/features/ops/hole.ts`: tool solids for each type along `(point, normal, up)`, through extension, evaluator integration as a generated tool. Test: simple/counterbore/countersink volumes against formulas, inclined normal axis, through exits, blind depth. +- [ ] 3.3 Move text shape code to `geometry/textShapes.ts` (Keychain tests stay green); `geometry/features/ops/text.ts` emboss/engrave with overlap offsets and curved-surface warning. Test: engraved volume ≈ glyph area × depth, embossed body stays one solid, warning on a cylinder. +- [ ] 3.4 Wire both operations into the stack of `41` (kinds, normalisation, summaries, migration passthrough). Test: round trip through `migrateScene`, failed-op behaviour. + +## 4. Document and worker + +- [ ] 4.1 `doc.cuts`, `cutPart` sources, normalisation and orphan handling; `generate('editor')` derives parts from groups (source evaluated once per group, cache key by group definition). Test: deleting the group restores the source, parts rebuilt after reload, source edit updates parts, cache hit counts. +- [ ] 4.2 Export: derived parts with unique names, loose pins as parts, tool/ghost rules of `41` unchanged. Test the export set and names. + +## 5. UI (no artboard exists: built from `ui/` components and tokens) + +- [ ] 5.1 Cut tool: plane widget in `Viewport` with the gizmo, axis snapping, plane from face (uses `42` picking), three points, mode selector (both / keep one / slice N), cut position field (`LengthField`), preview of the section in an inset panel. Component tests for the form and helpers for the plane maths; browser check. +- [ ] 5.2 Split dialog and group UI in the outliner (group, parts, source, explode gap slider, per-cut positions), oversize marks. Component tests. +- [ ] 5.3 `ConnectorEditor`: type and preset pickers, size/depth/clearance fields, count, margin, section preview with draggable markers, issue list with keys. Component tests: drag, add, remove, issues shown. +- [ ] 5.4 Hole tool: surface picking marker, form with type and preset, offsets, depth/through, live preview; Text tool form with font picker (reuse the list), size, spacing, depth/height, rotation, placement by click; both create stack operations. Component tests and a browser check on an inclined face. +- [ ] 5.5 Strings in `en.json` and `ru.json` (`cut.*`, `hole.*`, `text.*`, `cut.connector.*`, log keys); key parity. + +## 6. Version + +- [ ] 6.1 Bump `package.json` to 1.14.0 and add `1.14.0 — 43-editor-cut-split` to `CHANGELOG.md`. Test: `site.test.ts`. + +## 7. Verification + +- [ ] 7.1 `npm test` and `npm run build` are green, the dev server starts, no console errors; the Gridfinity baseplate split and connectors work as before. +- [ ] 7.2 In the browser, en and ru, desktop and 390 px: split a long model for a Bambu bed with pegs and magnets, print-check the part sizes in the export panel, move a cut and see parts update, cut at 30° from a face, put M3 counterbored holes and magnet pockets on a top face and a tilted face, add engraved and embossed text including Cyrillic, export STL/3MF and verify bbox, volumes and part names.