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
+8 -1
View File
@@ -687,6 +687,8 @@ func _enter_edit_shared() -> void:
var body := node as RigidBody2D
body.freeze_mode = RigidBody2D.FREEZE_MODE_KINEMATIC
body.freeze = true
if node is PropBlock:
(node as PropBlock).end_play_instance_scale()
_restore_authored_state()
# The freeze/teleport can take a physics frame or two to settle in the engine,
# so re-assert the authored transform on the next few physics frames.
@@ -767,6 +769,8 @@ func _enter_play_mode() -> void:
_collision_pairs.clear()
for node: Node2D in _world_children_selectable():
if node is RigidBody2D:
if node is PropBlock:
(node as PropBlock).begin_play_instance_scale()
(node as RigidBody2D).freeze = false
var rig_count := 0
for node: Node2D in _world_children_selectable():
@@ -810,6 +814,8 @@ func _restore_authored_state() -> void:
continue
node.global_position = entry.get("position", node.global_position)
node.global_rotation = entry.get("rotation", node.global_rotation)
if entry.has("scale"):
node.scale = entry["scale"]
if node is STICKMAN_RIG and entry.has("facing_profile"):
(node as STICKMAN_RIG).set_facing_profile(int(entry["facing_profile"]))
if node is RigidBody2D:
@@ -1293,7 +1299,8 @@ func _collect_scene_data(is_autosave: bool = false) -> Dictionary:
elif type == "prop":
var prop := n as PropBlock
entry["asset_id"] = prop.spawn_id
entry["scale"] = STAGE_SCENE_IO.vector_to_data(prop.scale)
var prop_scale: Vector2 = authored.get("scale", prop.scale)
entry["scale"] = STAGE_SCENE_IO.vector_to_data(prop_scale)
entry["material_preset"] = prop.material_preset
elif type == "area":
var area := n as TriggerArea