Paste where the view shows it, editor offsets included (#263)
Paste mixed two frames. The pointer comes from the Scene View, which draws every object moved by the editor offsets of it and its parents. Paste turned that point into a local position through the parents' plain world transform, so the copy landed beside the pointer by the offset of the parent it went into. Without a pointer, an original with an offset of its own had its copy placed beside its game position, which the view does not show. - A copied root now remembers where the view showed it (`ClipRoot.shown`, from `editorTransform`) instead of its world position. - New `editorLocalPositionFor` in `properties/transform.ts` turns a shown point into a local position through the parent chain with the editor offsets, as the view draws it. Paste uses it. - The copy itself still gets no editor offset of its own, as since #240. In the game it stands where it was drawn, minus the offsets of its parents. - Re-parenting under a parent with an offset still keeps the game position, so the object jumps in the view by that offset. That is a different rule from paste and is left as it is. Tests: - Unit (clipboard, groups, references, transform): 50 passed, 2 new. - e2e 51 on melektaus-atx: ALL PASSED, 12 checks, 1 new (51f). - Gate on melektaus-atx: 195 files, 2829 tests, CHECK EXIT 0. - Counter-checks: without the paste fix both new unit tests fail (copy at 540/470 instead of 500/500) and 51f fails by exactly the 60/-40 offset. With the root remembering its world position again, the own-offset unit test fails. - No full run. The e2e total of 1591 is 1590 plus 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9cff5282b6
commit
6d4247ebf8
7 files changed
+100
-23
No files matched your search
@@ -11,7 +11,7 @@ import { placementRejection } from '../model/placement';
|
||||
import type { Vec2 } from '../model/Properties';
|
||||
import { findAsset, type Project } from '../model/Project';
|
||||
import type { SceneObject } from '../model/SceneObject';
|
||||
import { carryTransform, localPositionFor, parentTransform } from '../properties/transform';
|
||||
import { carryTransform, editorLocalPositionFor, parentTransform } from '../properties/transform';
|
||||
import { type Command, CommandError } from './Command';
|
||||
import { requireGraph, requireHolder, requireNodeDef, requireStructuralObject } from './require';
|
||||
|
||||
@@ -22,8 +22,8 @@ function freshIds(ids: readonly string[]): Map<string, string> {
|
||||
|
||||
/**
|
||||
* Puts copied objects into a scene or container (#240). Each root goes under the parent `parents`
|
||||
* names for it ('' or missing = top level), shifted by `shift` in world units, and keeps its look
|
||||
* the way a re-parent does.
|
||||
* names for it ('' or missing = top level), shifted by `shift` from where the editor showed it, and
|
||||
* keeps its look the way a re-parent does.
|
||||
*/
|
||||
export class PasteObjectsCommand implements Command {
|
||||
readonly label: string;
|
||||
@@ -69,10 +69,10 @@ export class PasteObjectsCommand implements Command {
|
||||
copy.parentId = parentId;
|
||||
// The copy stays in its group when it lands at the top level of a holder that has the group.
|
||||
if (parentId || !findGroup(holder, copy.groupId ?? '')) delete copy.groupId;
|
||||
copy.position = localPositionFor(project, holder, parentId, {
|
||||
x: root.world.x + this.shift.x,
|
||||
y: root.world.y + this.shift.y,
|
||||
z: root.world.z,
|
||||
copy.position = editorLocalPositionFor(project, holder, parentId, {
|
||||
x: root.shown.x + this.shift.x,
|
||||
y: root.shown.y + this.shift.y,
|
||||
z: root.shown.z,
|
||||
});
|
||||
const after = parentTransform(project, holder, parentId).m;
|
||||
carryTransform(findAsset(project, object.assetId)!, copy, root.parentMatrix, after);
|
||||
|
||||
Reference in new issue
Block a user