Skip to content

fix: preserve constraint endpoints when converting shapes to polygons (#27)#76

Open
1egoman wants to merge 1 commit into
mainfrom
27_convert-shape-constraint-relinking-v2
Open

fix: preserve constraint endpoints when converting shapes to polygons (#27)#76
1egoman wants to merge 1 commit into
mainfrom
27_convert-shape-constraint-relinking-v2

Conversation

@1egoman

@1egoman 1egoman commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

When converting a rectangle or ellipse to a polygon (via ConvertToPolygonAction or TrimSplitTool), any attached constraints were being silently dropped. The old shape was deleted via deleteByIdDirect, which cascades to delete all constraints referencing it.

Approach

The fix relinks constraint endpoints before deleting the old shape, mapping locked-rectangle/locked-ellipse endpoints to locked-polygon endpoints:

  1. Rectangle corners: upperLeft → pointIndex 0, upperRight → 1, lowerRight → 2, lowerLeft → 3
  2. Ellipse perimeter: top → 0, right → 1, bottom → 2, left → 3
  3. Center points (both shapes): converted to free point endpoints since there is no corresponding polygon vertex
  4. Other endpoints (locked to other shapes, free points): left unchanged

Each constraint update is recorded via the appropriate *MoveEndpoints undo entry (linear, perpendicular, parallel, horizontal, vertical, colinear), ensuring undo/redo properly restores original endpoints.

What changed from the previous attempt (#38)

The previous PR (#38) restructured the entire GeometryStore from ECS components to separate arrays — this was too invasive and affected the entire codebase.

This fix takes a targeted approach: only the conversion methods are modified, no architecture changes needed. The key insight is that by relinking constraints before deleteByIdDirect, the cascade deletion no longer applies.

Assumptions

  • Point mappings assume the standard rectangleToPolygon and ellipseToPolygon outputs
  • Only locked-rectangle and locked-ellipse endpoints are remapped; locked-polygon and locked-datum pass through unchanged

Fixes #27

…#27)

When converting a rectangle or ellipse to a polygon, constraints that
referenced the old shape were being silently dropped because
deleteByIdDirect cascades to delete attached constraints. This commit:

- Adds _relinkEndpointForShapeConversion() to map locked-rectangle and
  locked-ellipse endpoints to locked-polygon (or free point for center)
- Adds _relinkAndRecordConstraint() to update constraints and record
  the appropriate undo entries for all constraint types
- Restructures convertRectangleToPolygon and convertEllipseToPolygon to
  relink constraints before deleting the old shape, preventing cascade
  deletion
- Uses a transaction so undo/redo properly restores original endpoints
- Adds 14 comprehensive tests covering perimeter points, center points,
  resolvability, undo/redo, multiple constraints, horizontal/vertical
  constraints, and non-matching endpoints
@1egoman 1egoman added the robot Created by a LLM to try to fix super trivial issues. label Jul 3, 2026

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review

Comment on lines 1074 to 1091
this.historyManager.applyTransaction('polygon-to-rectangle', () => {
this.historyManager.apply(UndoEntry.rectangleToPolygon(geometry, polygon));
// 1. Add the new polygon (record undo entry so it's removed on undo)
this.addDirect(polygon);
this.historyManager.push(UndoEntry.insert(polygon));

// 2. Relink any existing constraints from rectangle to polygon BEFORE the rectangle is deleted
for (const constraint of existingConstraints) {
this._relinkAndRecordConstraint(constraint, rectangleId, polygon.id, 'rectangle', center);
}

// 3. Delete the old rectangle (no cascade since constraints are already relinked)
this.deleteByIdDirect(rectangleId);
this.historyManager.push(UndoEntry.deleteGeometry(geometry));

// 4. Add H/V constraints for the new polygon edges
for (const template of constraintTemplates) {
this.addConstraint(template);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Old rectangleToPolygon and ellipseToPolygon UndoEntry types are now dead code

The old UndoEntry.rectangleToPolygon() and UndoEntry.ellipseToPolygon() factory functions (src/lib/history/types.ts:529-542) and their corresponding entry types (RectangleToPolygonEntry, EllipseToPolygonEntry at src/lib/history/types.ts:168-183) are no longer produced by any code path. The HistoryManager still has applyForward/applyReverse cases for 'rectangle-to-polygon' and 'ellipse-to-polygon' entry types (src/lib/history/HistoryManager.ts:343-350, 587-594). These are dead code now that the conversion uses insert + delete + move-endpoints entries instead. They may need to remain for backward compatibility with serialized undo stacks from older saves, but if not, they should be cleaned up.

(Refers to lines 1074-1093)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

robot Created by a LLM to try to fix super trivial issues.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Converting an ellipse/rectangle to a polygon drops any attached constraints

1 participant