feat: initial commit web-cad-neu with docker-compose, frontend and backend
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
# 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:
|
||||
```typescript
|
||||
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:
|
||||
```typescript
|
||||
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:
|
||||
```typescript
|
||||
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
|
||||
Reference in New Issue
Block a user