Files
web-cad/phase4-task-spec.md

6.7 KiB

Phase 4: Bearbeitungs-Tools (Modification Tools)

Requirements

  • F-CAD-02: Verschieben (MOVE), Kopieren (COPY/CO), Rotieren (ROTATE/RO), Skalieren (SCALE/SC), Spiegeln (MIRROR/MI), Trimmen (TRIM), Verlängern (EXTEND), Abrunden (FILLET), Versatz (OFFSET)
  • F-CAD-07: Auswahl-Methoden (already in SelectionEngine, ensure modification tools use it)
  • F-CAD-08: Gruppierung — Elemente zu Gruppen zusammenfassen, verschachteln, speichern

Current State

  • src/interaction/index.ts: handleModifyDown() is a stub (lines 411-421) — only checks if selection exists, sets phase to 'modifying', pushes point. No actual modification logic.
  • onElementsModified callback exists but is never called.
  • App.tsx has handleElementsDeleted but no handleElementsModified.
  • No tool classes exist in src/tools/.

Architecture Decision

Instead of separate tool class files (BAUPLAN suggests 10+ files), implement modification logic directly in InteractionEngine with a handleModifyMove(pt) method that handles all modify tools via switch. This is simpler, matches the existing pattern (handleDrawDown handles all draw tools), and avoids over-engineering. Create a src/tools/modification/ directory with pure geometry utility functions that InteractionEngine calls.

Tasks

Task 1: Create geometry utility functions

File: src/tools/modification/geometry.ts

Pure functions for element transformation:

export function moveElement(el: CADElement, dx: number, dy: number): CADElement
export function rotateElement(el: CADElement, cx: number, cy: number, angle: number): CADElement
export function scaleElement(el: CADElement, cx: number, cy: number, sx: number, sy: number): CADElement
export function mirrorElement(el: CADElement, x1: number, y1: number, x2: number, y2: number): CADElement
export function offsetElement(el: CADElement, distance: number): CADElement
export function trimElement(el: CADElement, boundary: CADElement): CADElement | null
export function extendElement(el: CADElement, boundary: CADElement): CADElement | null
export function filletElements(el1: CADElement, el2: CADElement, radius: number): [CADElement, CADElement] | null

Each function returns a NEW CADElement (immutable). For lines: update x1/y1/x2/y2 in properties. For circles: update x/y/radius. For polylines: update points array. For text: update x/y. Apply rotation to properties.rotation.

Task 2: Implement modification logic in InteractionEngine

File: src/interaction/index.ts

Replace the stub handleModifyDown with real multi-step logic:

Move/Copy (2 clicks):

  1. First click: set base point (state.points[0])
  2. Mouse move: update preview with moved elements
  3. Second click: confirm — call onElementsModified with moved elements (Move) or onElementCreated for copies (Copy)
  4. Reset to idle

Rotate (2 clicks):

  1. First click: set pivot point
  2. Mouse move: preview rotation (angle = atan2(mouse - pivot))
  3. Second click: confirm with current angle

Scale (2 clicks):

  1. First click: set base point
  2. Mouse move: preview scale (factor = dist(mouse, base) / dist(firstElement, base))
  3. Second click: confirm

Mirror (2 clicks):

  1. First click: first point of mirror axis
  2. Mouse move: preview mirror
  3. Second click: second point of mirror axis, confirm

Trim/Extend (2 clicks):

  1. First click: select boundary element (hitTest)
  2. Second click: select element to trim/extend (hitTest), apply, confirm

Fillet (3 clicks):

  1. First click: select first element
  2. Second click: select second element
  3. Third click or Enter: set radius (use distance between elements or prompt)

Offset (2 clicks):

  1. First click: select element to offset
  2. Second click: direction + distance = offset

Add updateModifyPreview(pt) method similar to updatePreview(pt) but for modifications. Add confirmModify() method that calls onElementsModified with modified elements.

For Copy: call onElementCreated for each copied element instead of onElementsModified.

Task 3: Wire onElementsModified in App.tsx

File: src/App.tsx

  • Add handleElementsModified callback: push current elements to undoStack, replace modified elements by ID, clear redoStack
  • Pass onElementsModified to CanvasArea
  • CanvasArea wires it to interaction.setCallbacks({ onElementsModified: ... })

Task 4: Add GroupTool

File: src/tools/modification/GroupTool.ts

Simple group management:

export interface ElementGroup {
  id: string;
  name: string;
  elementIds: string[];
  parentGroupId: string | null;
}

export class GroupManager {
  private groups: Map<string, ElementGroup> = new Map();
  createGroup(elementIds: string[], name?: string): ElementGroup
  ungroup(groupId: string): void
  getGroup(id: string): ElementGroup | undefined
  getGroups(): ElementGroup[]
  getGroupedElements(): Set<string>  // all element IDs in any group
  moveGroup(groupId: string, dx: number, dy: number): string[]  // returns modified element IDs
}

Wire in App.tsx: add groups state, add command 'G' or 'GROUP' to create group from selection.

Task 5: Update command shortcuts

File: src/App.tsx

Add to handleCommand:

  • M or MOVE → setTool('move')
  • CO or COPY → setTool('copy')
  • RO or ROTATE → setTool('rotate')
  • SC or SCALE → setTool('scale')
  • MI or MIRROR → setTool('mirror')
  • TR or TRIM → setTool('trim')
  • EX or EXTEND → setTool('extend')
  • F or FILLET → setTool('fillet')
  • O or OFFSET → setTool('offset')
  • E or ERASE → setTool('delete')
  • G or GROUP → create group from selection
  • UNG or UNGROUP → ungroup selection

Note: Some of these already exist from Phase 3. Check and avoid duplicates.

Task 6: Update CanvasAreaProps

File: src/types/ui.types.ts

Add onElementsModified to CanvasAreaProps:

onElementsModified: (els: CADElement[]) => void;

Constraints

  • TypeScript functional components, no inline styles (except where mockup uses style)
  • No styled-components, no Tailwind
  • npx tsc --noEmit must pass with 0 errors
  • npx vite build must succeed
  • Minimal changes — extend existing files, don't rewrite
  • Keep all existing CSS class names
  • Pure geometry functions in separate file, not in InteractionEngine

File Summary

  1. src/tools/modification/geometry.ts — NEW: pure geometry transform functions
  2. src/tools/modification/GroupTool.ts — NEW: GroupManager class
  3. src/interaction/index.ts — PATCH: implement handleModifyDown, updateModifyPreview, confirmModify
  4. src/App.tsx — PATCH: handleElementsModified, groups state, command shortcuts
  5. src/types/ui.types.ts — PATCH: add onElementsModified to CanvasAreaProps
  6. src/components/CanvasArea.tsx — PATCH: wire onElementsModified callback

Test Criteria

  1. npx tsc --noEmit — 0 errors
  2. npx vite build — success