Implement Phase 10a - Prop Scaling

- Added specifications for Phase 10a, introducing permanent size changes to `.prop` documents and reversible uniform instance scaling in Sandbox EDIT mode.
- Updated `PropCanvas`, `PropAnchorData`, `PropEditor`, `StageGizmos`, `SandboxStage`, and `StageSelection` classes to support new scaling features.
- Implemented document transactions for scaling, including normalization to a 96 px maximum dimension.
- Introduced a new **Transform Whole Prop** tool in the Prop Editor for moving, rotating, and resizing props.
- Created unit tests for the new scaling functionality, validating shape scaling, anchor transformations, and whole-prop interactions.
- Ensured compatibility with existing prop formats and maintained the integrity of the document structure during scaling operations.
This commit is contained in:
2026-09-22 22:23:31 -04:00
parent 97f3730413
commit 0a7f97cec7
18 changed files with 1782 additions and 78 deletions
+7 -1
View File
@@ -112,17 +112,23 @@ func _test_custom_prop_lifecycle() -> void:
await process_frame
var prop: PropBlock = stage._spawner.spawn_prop_asset(TEMP_PROP_PATH, Vector2(160, -40))
prop.rotation = deg_to_rad(20.0)
prop.scale = Vector2(1.5, 1.5)
stage._finalize_placed_node(prop)
var authored_position: Vector2 = prop.global_position
var authored_rotation: float = prop.global_rotation
var authored_scale: Vector2 = prop.scale
_check(prop.freeze, "custom prop is frozen in EDIT")
stage.set_mode(stage.StageMode.PLAY)
_check(not prop.freeze, "custom prop unfreezes in PLAY")
var play_scene: Dictionary = stage._collect_scene_data()
var play_entry: Dictionary = (play_scene.get("objects", []) as Array)[0]
var saved_scale: Vector2 = Vector2(float((play_entry.get("scale", {}) as Dictionary).get("x", 0.0)), float((play_entry.get("scale", {}) as Dictionary).get("y", 0.0)))
_check(saved_scale.is_equal_approx(authored_scale), "save during PLAY preserves authored prop scale")
prop.global_position += Vector2(90, 70)
prop.global_rotation += 0.4
prop.linear_velocity = Vector2(50, 20)
prop.angular_velocity = 2.0
stage.set_mode(stage.StageMode.EDIT)
_check(prop.freeze and prop.global_position.is_equal_approx(authored_position) and is_equal_approx(prop.global_rotation, authored_rotation) and prop.linear_velocity.is_zero_approx() and is_zero_approx(prop.angular_velocity), "PLAY to EDIT restores custom prop authored pose and clears velocity")
_check(prop.freeze and prop.global_position.is_equal_approx(authored_position) and is_equal_approx(prop.global_rotation, authored_rotation) and prop.scale.is_equal_approx(authored_scale) and prop.linear_velocity.is_zero_approx() and is_zero_approx(prop.angular_velocity), "PLAY to EDIT restores authored pose, keeps scale, and clears velocity")
stage.queue_free()
await process_frame