Skip to content

Commit aa1a62f

Browse files
author
DavidQ
committed
Correct Object Vector V2 flame anchoring by fixing scale behavior instead of zoom behavior - PR_26133_102-object-vector-scale-anchor-fix
1 parent d074899 commit aa1a62f

3 files changed

Lines changed: 80 additions & 51 deletions

File tree

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# PR_26133_102 Object Vector Scale Anchor Fix
2+
3+
## Scope
4+
- Read `docs/dev/PROJECT_INSTRUCTIONS.md` before implementation.
5+
- Used the currently integrated PR_26133_101 state as the prior reference. The prior delta ZIP `tmp/PR_26133_101-object-vector-zoom-and-layout-fixes_delta.zip` was not present locally.
6+
- Limited changes to the Object Vector Studio V2 scale-anchor correction and focused Playwright coverage.
7+
8+
## Implementation
9+
- Removed the prior zoom-anchoring/preview-state workaround from `ToolStarterApp.js`:
10+
- `objectScalePreviewValues`
11+
- `currentObjectScalePreviewValue`
12+
- `transformWithRelativeScaleAroundPivot`
13+
- Replaced it with object scale math based on current per-shape transforms:
14+
- Object Scale input derives from the selected object shape transforms.
15+
- Applying Object Scale computes each shape's current scale ratio against the requested scale.
16+
- Each shape origin is moved around the object pivot by that ratio, then `scaleX`/`scaleY` are set to the requested uniform scale.
17+
- Kept PR_26133_101 zoom/layout behavior intact, including `MAX_ZOOM = 1.0`.
18+
19+
## Targeted Validation
20+
- PASS: `node --check tools\object-vector-studio-v2\js\ToolStarterApp.js`
21+
- PASS: `node --check tests\playwright\tools\WorkspaceManagerV2.spec.mjs`
22+
- PASS: targeted Object Vector V2 Playwright validation:
23+
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list -g "shows Object Vector Studio V2 layout shell|expands Object Vector Studio V2 asset authoring controls"`
24+
- Result: 2 passed.
25+
- PASS: `npm run test:workspace-v2`
26+
- Result: 56 passed.
27+
- PASS: `git diff --check -- tools/object-vector-studio-v2/js/ToolStarterApp.js tests/playwright/tools/WorkspaceManagerV2.spec.mjs`
28+
- Only CRLF warning for the existing Playwright test file.
29+
30+
## Scale / Zoom Notes
31+
- The object preview zoom contract from PR_26133_101 was not reworked.
32+
- The anchoring fix is now in Object Scale behavior: repeated object scale changes derive from each shape's actual current transform instead of a separate preview tracking map.
33+
- The focused Playwright assertions verify the prior preview-state helper is gone and that object scale offsets follow the requested scale rather than compounding from stale preview state.
34+
35+
## Manual Verification Guidance
36+
- Open Object Vector Studio V2 with the Asteroids ship object selected.
37+
- Use Object Transform scale repeatedly, for example `1.10` then `1.09`.
38+
- Verify flame/inner line shapes remain anchored to the bottom hull lines instead of drifting upward.
39+
- Verify preview zoom still reaches the PR_26133_101 max zoom behavior and Tools/fullscreen layout remains usable.
40+
41+
## Diff Stat
42+
```
43+
tests/playwright/tools/WorkspaceManagerV2.spec.mjs | 3 +
44+
tools/object-vector-studio-v2/js/ToolStarterApp.js | 79 ++++++++--------------
45+
2 files changed, 31 insertions(+), 51 deletions(-)
46+
```
47+
48+
## Full Samples Smoke Test
49+
Skipped per PR_26133_102 instructions.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3438,6 +3438,9 @@ test.describe("Workspace Manager V2 bootstrap", () => {
34383438
expect(zoomSource).toContain("const MAX_ZOOM = 1.0;");
34393439
expect(zoomSource).toContain("const MIN_ZOOM = 0.01;");
34403440
expect(zoomSource).toContain("const ZOOM_STEP = 0.01;");
3441+
expect(zoomSource).toContain("transformWithObjectScaleAroundPivot");
3442+
expect(zoomSource).not.toContain("objectScalePreviewValues");
3443+
expect(zoomSource).not.toContain("transformWithRelativeScaleAroundPivot");
34413444
expect(zoomSource).toMatch(/formatZoomPercentage\(\) \{\s+return Math\.round\(this\.viewport\.zoom \* 100\);\s+\}/);
34423445
expect(zoomSource.match(/formatZoomPercentage\(\) \* 10/g)?.length || 0).toBeGreaterThanOrEqual(4);
34433446
expect(zoomSource).not.toMatch(/viewport\.zoom\s*\*\s*100\s*\*\s*GRID_STEP|const MAX_ZOOM = 2/);

tools/object-vector-studio-v2/js/ToolStarterApp.js

Lines changed: 28 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,6 @@ export class ToolStarterApp {
525525
this.previewRedoStack = [];
526526
this.previewPointerEdit = null;
527527
this.transformInputValues = new Map();
528-
this.objectScalePreviewValues = new Map();
529528
this.stateControlStateId = "";
530529
this.pendingAddObjectClick = false;
531530
this.hiddenObjectIds = new Set();
@@ -2806,8 +2805,7 @@ export class ToolStarterApp {
28062805
const section = document.createElement("section");
28072806
section.className = "object-vector-studio-v2__edit-panel object-vector-studio-v2__edit-panel--transform object-vector-studio-v2__edit-panel--object-transform";
28082807
const origin = this.objectTransformOrigin(object);
2809-
const objectScaleInput = Number(this.objectScalePreviewValues.get(object.id) ?? 1);
2810-
const objectScale = Number.isFinite(objectScaleInput) && objectScaleInput > 0 ? objectScaleInput : 1;
2808+
const objectScale = this.objectScaleControlValue(object);
28112809
section.append(
28122810
this.createObjectOriginControlRow(origin),
28132811
this.createObjectRotateControlRow(),
@@ -2827,6 +2825,18 @@ export class ToolStarterApp {
28272825
return section;
28282826
}
28292827

2828+
objectScaleControlValue(object) {
2829+
const frame = object?.id === this.selectedObjectId ? this.activeFrame() : null;
2830+
const transforms = sortedShapes(object)
2831+
.map((shape, shapeIndex) => this.ensureShapeTransform(this.effectiveShapeForFrame(shape, frame, shapeIndex)));
2832+
if (!transforms.length) {
2833+
return 1;
2834+
}
2835+
const firstScale = transforms[0].scaleX;
2836+
const sameScale = transforms.every((transform) => Math.abs(transform.scaleX - transform.scaleY) < 0.001 && Math.abs(transform.scaleX - firstScale) < 0.001);
2837+
return sameScale && firstScale > 0 ? this.formatViewportNumber(firstScale) : 1;
2838+
}
2839+
28302840
createMoveControlRow() {
28312841
return this.createTransformAxisControlRow({
28322842
action: () => this.moveSelectedShape(),
@@ -4101,17 +4111,25 @@ export class ToolStarterApp {
41014111
};
41024112
}
41034113

4104-
transformWithRelativeScaleAroundPivot(transform, pivot, scaleRatio) {
4114+
transformWithObjectScaleAroundPivot(transform, pivot, scale) {
41054115
const normalized = this.ensureShapeTransform({ transform });
4116+
if (Math.abs(normalized.scaleX - normalized.scaleY) >= 0.001) {
4117+
throw new Error("shape transform scale must be uniform for Object Scale.");
4118+
}
4119+
const currentScale = normalized.scaleX;
4120+
const scaleRatio = scale / currentScale;
4121+
if (!Number.isFinite(scaleRatio) || scaleRatio <= 0) {
4122+
throw new Error("scale ratio must be greater than 0.");
4123+
}
41064124
const originWorld = this.transformedPoint(normalized.shapeOrigin, normalized);
41074125
const nextOriginWorld = {
41084126
x: this.formatViewportNumber(pivot.x + (originWorld.x - pivot.x) * scaleRatio),
41094127
y: this.formatViewportNumber(pivot.y + (originWorld.y - pivot.y) * scaleRatio)
41104128
};
41114129
return {
41124130
...normalized,
4113-
scaleX: this.formatViewportNumber(normalized.scaleX * scaleRatio),
4114-
scaleY: this.formatViewportNumber(normalized.scaleY * scaleRatio),
4131+
scaleX: this.formatViewportNumber(scale),
4132+
scaleY: this.formatViewportNumber(scale),
41154133
x: this.formatViewportNumber(nextOriginWorld.x - normalized.shapeOrigin.x),
41164134
y: this.formatViewportNumber(nextOriginWorld.y - normalized.shapeOrigin.y)
41174135
};
@@ -7385,54 +7403,20 @@ export class ToolStarterApp {
73857403
this.transformInputValues.set(inputElement.id, inputElement.value);
73867404
}
73877405
this.applySelectedObjectScaleValue(nextScale, {
7388-
previousScale: input.value,
73897406
okMessage: `OK Object scale preview set to ${this.formatScaleInputValue(nextScale)} for ${this.selectedObject()?.name || "selected object"}.`
73907407
});
73917408
}
73927409

7393-
currentObjectScalePreviewValue(object = this.selectedObject()) {
7394-
const storedScale = this.objectScalePreviewValues.get(object?.id);
7395-
return Number.isFinite(storedScale) && storedScale > 0 ? storedScale : 1;
7396-
}
7397-
7398-
applySelectedObjectScaleValue(scale, { okMessage, previousScale } = {}) {
7410+
applySelectedObjectScaleValue(scale, { okMessage } = {}) {
73997411
const origin = this.readObjectOriginInputs();
74007412
if (!origin.ok) {
74017413
this.statusLog.write(`FAIL Invalid object transform rejected: ${origin.error}`);
74027414
return false;
74037415
}
74047416
const object = this.selectedObject();
7405-
const priorScale = Number.isFinite(previousScale) && previousScale > 0
7406-
? previousScale
7407-
: this.currentObjectScalePreviewValue(object);
7408-
const scaleRatio = scale / priorScale;
7409-
if (!Number.isFinite(scaleRatio) || scaleRatio <= 0) {
7410-
this.statusLog.write("FAIL Invalid object transform rejected: scale ratio must be greater than 0.");
7411-
return false;
7412-
}
7413-
const priorStoredScale = object?.id ? this.objectScalePreviewValues.get(object.id) : undefined;
7414-
const priorInputScale = this.transformInputValues.get("objectVectorStudioV2ObjectScaleInput");
7415-
const nextScale = this.formatViewportNumber(scale);
7416-
if (object?.id) {
7417-
this.objectScalePreviewValues.set(object.id, nextScale);
7418-
this.transformInputValues.set("objectVectorStudioV2ObjectScaleInput", this.formatScaleInputValue(nextScale));
7419-
}
7420-
const committed = this.updateSelectedObjectTransforms("scale", (shape) => {
7421-
shape.transform = this.transformWithRelativeScaleAroundPivot(this.ensureShapeTransform(shape), origin.value, scaleRatio);
7417+
return this.updateSelectedObjectTransforms("scale", (shape) => {
7418+
shape.transform = this.transformWithObjectScaleAroundPivot(this.ensureShapeTransform(shape), origin.value, scale);
74227419
}, okMessage || `OK Object scale preview set to ${this.formatScaleInputValue(scale)} for ${object?.name || "selected object"}.`, "Object Transform scale failed schema validation");
7423-
if (!committed && object?.id) {
7424-
if (priorStoredScale === undefined) {
7425-
this.objectScalePreviewValues.delete(object.id);
7426-
} else {
7427-
this.objectScalePreviewValues.set(object.id, priorStoredScale);
7428-
}
7429-
if (priorInputScale === undefined) {
7430-
this.transformInputValues.delete("objectVectorStudioV2ObjectScaleInput");
7431-
} else {
7432-
this.transformInputValues.set("objectVectorStudioV2ObjectScaleInput", priorInputScale);
7433-
}
7434-
}
7435-
return committed;
74367420
}
74377421

74387422
resizeSelectedObject() {
@@ -7463,18 +7447,12 @@ export class ToolStarterApp {
74637447
}
74647448

74657449
const nextPayload = this.cloneCurrentPayload();
7466-
const currentScale = this.currentObjectScalePreviewValue(object);
7467-
const scaleRatio = input.value / currentScale;
7468-
if (!Number.isFinite(scaleRatio) || scaleRatio <= 0) {
7469-
this.statusLog.write("FAIL Resize Geometry rejected for selected object: scale ratio must be greater than 0.");
7470-
return;
7471-
}
74727450
try {
74737451
targetIndexes.forEach((shapeIndex) => {
74747452
const baseShape = this.findShapeInPayload(nextPayload, shapeIndex);
74757453
const override = this.frameOverrideInPayload(nextPayload, shapeIndex, { create: false });
74767454
const transformTarget = override?.transform ? { transform: override.transform, geometry: baseShape.geometry } : baseShape;
7477-
const transform = this.transformWithRelativeScaleAroundPivot(this.ensureShapeTransform(transformTarget), origin.value, scaleRatio);
7455+
const transform = this.transformWithObjectScaleAroundPivot(this.ensureShapeTransform(transformTarget), origin.value, input.value);
74787456
this.resizeShapeGeometryByTransformScale(baseShape, transform);
74797457
transform.scaleX = 1;
74807458
transform.scaleY = 1;
@@ -7494,7 +7472,6 @@ export class ToolStarterApp {
74947472
}
74957473

74967474
this.transformInputValues.set("objectVectorStudioV2ObjectScaleInput", "1");
7497-
this.objectScalePreviewValues.set(object.id, 1);
74987475
this.commitPayloadUpdate(
74997476
nextPayload,
75007477
object.id,

0 commit comments

Comments
 (0)