fix(studio): Add solid floor collision, live 3D gizmo dragging, inspector property sync, and nudge shortcuts
checkpoint / checkpoint (push) Successful in 6m23s

This commit is contained in:
2026-09-22 21:07:12 +00:00
parent 3f0ba543e1
commit 8019a1321c
5 changed files with 843 additions and 92 deletions
+158
View File
@@ -0,0 +1,158 @@
extends SceneTree
## Headless smoke test verifying Studio floor collision, selection,
## gizmo ray picking, inspector property sync, and transformation manipulation.
var _failures: int = 0
var _checks: int = 0
const StudioMainClass = preload("res://scripts/studio/studio_main.gd")
const TransformGizmo3DClass = preload("res://scripts/studio/gizmos/transform_gizmo_3d.gd")
func _init() -> void:
print("--- Running Smoke Test: Studio Viewport Interaction & Floor Collision ---")
test_floor_collision_body()
test_inspector_sync_and_visual_transform()
test_gizmo_picking_and_dragging()
test_nudge_shortcuts_and_delete()
if _failures == 0:
print("--- PASS: All %d studio interaction checks passed! ---" % _checks)
quit(0)
else:
printerr("--- FAIL: %d failures out of %d checks ---" % [_failures, _checks])
quit(1)
func _check(desc: String, cond: bool) -> void:
_checks += 1
if cond:
print(" ✓ %s" % desc)
else:
_failures += 1
printerr(" ✗ FAIL: %s" % desc)
func test_floor_collision_body() -> void:
print("Testing Studio floor collision setup...")
var studio := StudioMainClass.new()
root.add_child(studio)
var floor_body = studio._sub_viewport.find_child("StudioFloorBody", true, false)
_check("StudioFloorBody exists in 3D viewport", floor_body != null)
if floor_body is StaticBody3D:
var sb := floor_body as StaticBody3D
_check("Floor body has collision layer 1 enabled", sb.get_collision_layer_value(1))
_check("Floor body has collision mask 1 enabled", sb.get_collision_mask_value(1))
var col = sb.find_child("*", true, false) as CollisionShape3D
_check("Floor body has CollisionShape3D", col != null and col.shape is BoxShape3D)
if col != null and col.shape is BoxShape3D:
var box := col.shape as BoxShape3D
_check("Floor collider spans at least 100x100m", box.size.x >= 100.0 and box.size.z >= 100.0)
studio.queue_free()
func test_inspector_sync_and_visual_transform() -> void:
print("Testing Inspector property sync to 3D visual nodes...")
var studio := StudioMainClass.new()
root.add_child(studio)
# Place an object
var obj_id := studio.core.place_object("crate_small", Vector3(10, 20, 0))
studio._spawn_visual_node(obj_id)
studio._on_selection_changed(obj_id)
var visual_node := studio._world_container.get_node_or_null(obj_id) as Node3D
_check("Visual node exists in world container", visual_node != null)
# Simulate inspector position change (moving object via Inspector)
studio._inspector_dock._pos_x.value = 15.0
studio._inspector_dock._pos_y.value = 3.0
studio._inspector_dock._pos_z.value = 25.0
studio._inspector_dock._on_pos_changed(0.0)
_check("Visual node position updated to match Inspector X", is_equal_approx(visual_node.position.x, 15.0))
_check("Visual node position updated to match Inspector Y (height)", is_equal_approx(visual_node.position.y, 3.0))
_check("Visual node position updated to match Inspector Z (depth)", is_equal_approx(visual_node.position.z, 25.0))
# Simulate inspector yaw rotation change
studio._inspector_dock._rot_y.value = 90.0
studio._inspector_dock._on_rot_changed(90.0)
_check("Visual node rotated to match Inspector Yaw", absf(visual_node.rotation_degrees.y - 90.0) < 0.1)
# Simulate inspector scale change
studio._inspector_dock._scale_x.value = 2.0
studio._inspector_dock._scale_y.value = 1.5
studio._inspector_dock._scale_z.value = 2.0
studio._inspector_dock._on_scale_changed(0.0)
_check("Visual node scale updated to match Inspector", is_equal_approx(visual_node.scale.x, 2.0) and is_equal_approx(visual_node.scale.y, 1.5))
studio.queue_free()
func test_gizmo_picking_and_dragging() -> void:
print("Testing Gizmo geometric picking and axis detection...")
var studio := StudioMainClass.new()
root.add_child(studio)
var obj_id := studio.core.place_object("bison", Vector3(0, 0, 0))
studio._spawn_visual_node(obj_id)
studio._on_selection_changed(obj_id)
var gizmo = studio._gizmo_3d
_check("Gizmo is attached and visible on selection", gizmo != null and gizmo.visible)
# Test X axis picking (ray aimed near +X arrow at (1.0, 0, 0) relative to gizmo)
var ray_origin: Vector3 = gizmo.position + Vector3(1.0, 5.0, 0.0)
var ray_dir: Vector3 = Vector3(0.0, -1.0, 0.0)
var picked_axis = gizmo.pick_axis(ray_origin, ray_dir)
_check("Gizmo ray picking correctly identified X axis", picked_axis == TransformGizmo3DClass.Axis.X)
# Test Y axis picking (ray aimed near +Y arrow at (0, 1.0, 0) relative to gizmo)
ray_origin = gizmo.position + Vector3(0.0, 1.0, 5.0)
ray_dir = Vector3(0.0, 0.0, -1.0)
picked_axis = gizmo.pick_axis(ray_origin, ray_dir)
_check("Gizmo ray picking correctly identified Y axis", picked_axis == TransformGizmo3DClass.Axis.Y)
# Test Z axis picking (ray aimed near +Z arrow at (0, 0, 1.0) relative to gizmo)
ray_origin = gizmo.position + Vector3(0.0, 5.0, 1.0)
ray_dir = Vector3(0.0, -1.0, 0.0)
picked_axis = gizmo.pick_axis(ray_origin, ray_dir)
_check("Gizmo ray picking correctly identified Z axis", picked_axis == TransformGizmo3DClass.Axis.Z)
# Test Center picking (ray aimed at gizmo origin)
ray_origin = gizmo.position + Vector3(0.0, 5.0, 0.0)
ray_dir = Vector3(0.0, -1.0, 0.0)
picked_axis = gizmo.pick_axis(ray_origin, ray_dir)
_check("Gizmo ray picking correctly identified Center/Plane handle", picked_axis == TransformGizmo3DClass.Axis.XY)
studio.queue_free()
func test_nudge_shortcuts_and_delete() -> void:
print("Testing keyboard nudge shortcuts and object deletion...")
var studio := StudioMainClass.new()
root.add_child(studio)
var obj_id := studio.core.place_object("crate_small", Vector3(0, 0, 0))
studio._spawn_visual_node(obj_id)
studio.core.select_object(obj_id)
studio._on_selection_changed(obj_id)
var visual_node := studio._world_container.get_node_or_null(obj_id) as Node3D
# Nudge position right (+X)
var old_x = visual_node.position.x
studio._nudge_selected_position(Vector3(1, 0, 0))
_check("Nudge right moved visual node +X by 1.0", is_equal_approx(visual_node.position.x, old_x + 1.0))
# Nudge rotation
studio._nudge_selected_rotation(45.0)
_check("Nudge rotation rotated visual node by 45 degrees", is_equal_approx(visual_node.rotation_degrees.y, 45.0))
# Delete object
studio._delete_selected_object(obj_id)
_check("Object deleted from Core model", studio.core.model.find_object(obj_id) == null)
_check("Selection cleared after delete", studio.core.selection.get_selected() == "")
studio.queue_free()