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
+58 -23
View File
@@ -2,6 +2,7 @@ class_name StudioInspectorDock
extends VBoxContainer
## Property Inspector Dock for ModBison Studio.
## Renders editable properties for selected world object(s) with undo/redo support.
## Synchronizes Godot 3D world transforms (X, Y_height, Z_depth) with Chunk storage.
signal property_changed(object_id: String, key: String, value: Variant)
signal delete_requested(object_id: String)
@@ -15,7 +16,7 @@ var _empty_label: Label
var _scroll: ScrollContainer
var _content_vbox: VBoxContainer
# Transform fields
# Transform fields (Godot 3D: X, Y_up, Z_depth)
var _pos_x: SpinBox
var _pos_y: SpinBox
var _pos_z: SpinBox
@@ -27,6 +28,7 @@ var _scale_z: SpinBox
# Metadata
var _name_edit: LineEdit
var _asset_label: Label
var _btn_delete: Button
func _init() -> void:
@@ -83,27 +85,40 @@ func _build_ui() -> void:
# Transform header
var t_header := Label.new()
t_header.text = "Transform"
t_header.text = "\nTransform (3D Space)"
t_header.add_theme_font_size_override("font_size", 11)
t_header.add_theme_color_override("font_color", Color(0.35, 0.85, 1.0))
_content_vbox.add_child(t_header)
_pos_x = _add_spin("Pos X", _on_pos_changed)
_pos_y = _add_spin("Pos Y", _on_pos_changed)
_pos_y = _add_spin("Pos Y (Up)", _on_pos_changed, -50.0, 500.0, 0.5)
_pos_z = _add_spin("Pos Z", _on_pos_changed)
_rot_y = _add_spin("Yaw (°)", _on_rot_changed, -360.0, 360.0, 1.0)
_rot_y = _add_spin("Yaw (°)", _on_rot_changed, -360.0, 360.0, 15.0)
_scale_x = _add_spin("Scale X", _on_scale_changed, 0.05, 50.0, 0.1)
_scale_y = _add_spin("Scale Y", _on_scale_changed, 0.05, 50.0, 0.1)
_scale_z = _add_spin("Scale Z", _on_scale_changed, 0.05, 50.0, 0.1)
_scale_x = _add_spin("Scale X", _on_scale_changed, 0.1, 50.0, 0.1)
_scale_y = _add_spin("Scale Y", _on_scale_changed, 0.1, 50.0, 0.1)
_scale_z = _add_spin("Scale Z", _on_scale_changed, 0.1, 50.0, 0.1)
# Actions
var act_sep := HSeparator.new()
_content_vbox.add_child(act_sep)
_btn_delete = Button.new()
_btn_delete.text = "🗑 Delete Object"
_btn_delete.add_theme_color_override("font_color", Color(1.0, 0.4, 0.4))
_btn_delete.pressed.connect(func():
if _selected_id != "":
delete_requested.emit(_selected_id)
)
_content_vbox.add_child(_btn_delete)
func _add_spin(label: String, callback: Callable, min_val: float = -500.0, max_val: float = 500.0, step: float = 0.5) -> SpinBox:
var row := HBoxContainer.new()
var lbl := Label.new()
lbl.text = label + ":"
lbl.custom_minimum_size = Vector2(70, 0)
lbl.custom_minimum_size = Vector2(80, 0)
lbl.add_theme_font_size_override("font_size", 10)
row.add_child(lbl)
@@ -118,6 +133,18 @@ func _add_spin(label: String, callback: Callable, min_val: float = -500.0, max_v
return spin
func set_transform_values(godot_pos: Vector3, yaw_deg: float, godot_scale: Vector3) -> void:
_updating = true
if _pos_x: _pos_x.value = godot_pos.x
if _pos_y: _pos_y.value = godot_pos.y
if _pos_z: _pos_z.value = godot_pos.z
if _rot_y: _rot_y.value = yaw_deg
if _scale_x: _scale_x.value = godot_scale.x
if _scale_y: _scale_y.value = godot_scale.y
if _scale_z: _scale_z.value = godot_scale.z
_updating = false
func _on_selection_changed(obj_id: String) -> void:
_selected_id = obj_id
if _selected_id == "" or _core == null:
@@ -141,16 +168,18 @@ func _on_selection_changed(obj_id: String) -> void:
_name_edit.text = dname
_asset_label.text = "Asset: %s" % obj.asset_id
_pos_x.value = obj.position.x
_pos_y.value = obj.position.y
_pos_z.value = obj.position.z
# Map chunk coords (X, Y_ground, Z_height) to Godot 3D (X, Y_height, Z_depth)
var godot_pos := Vector3(obj.position.x, obj.position.z, obj.position.y)
_pos_x.value = godot_pos.x
_pos_y.value = godot_pos.y
_pos_z.value = godot_pos.z
var euler = Quaternion(obj.rotation.x, obj.rotation.y, obj.rotation.z, obj.rotation.w).get_euler()
var euler = Quaternion(obj.rotation.x, obj.rotation.z, obj.rotation.y, obj.rotation.w).get_euler()
_rot_y.value = rad_to_deg(euler.y)
_scale_x.value = obj.scale.x
_scale_y.value = obj.scale.y
_scale_z.value = obj.scale.z
_scale_y.value = obj.scale.z
_scale_z.value = obj.scale.y
_updating = false
@@ -164,23 +193,29 @@ func _on_name_changed(new_name: String) -> void:
func _on_pos_changed(_val: float) -> void:
if _updating or _core == null or _selected_id == "":
return
var new_pos := Vector3(_pos_x.value, _pos_y.value, _pos_z.value)
_core.move_object(_selected_id, new_pos)
property_changed.emit(_selected_id, "position", new_pos)
var godot_pos := Vector3(_pos_x.value, _pos_y.value, _pos_z.value)
# Convert Godot 3D (X, Y_up, Z_depth) to Chunk (X, Y_ground, Z_height)
var chunk_pos := Vector3(godot_pos.x, godot_pos.z, godot_pos.y)
_core.move_object(_selected_id, chunk_pos)
property_changed.emit(_selected_id, "position", godot_pos)
func _on_rot_changed(_val: float) -> void:
if _updating or _core == null or _selected_id == "":
return
var quat := Quaternion.from_euler(Vector3(0, deg_to_rad(_rot_y.value), 0))
var rot_vec := Vector4(quat.x, quat.y, quat.z, quat.w)
var deg := _rot_y.value
var godot_q := Quaternion(Vector3.UP, deg_to_rad(deg))
# Convert to chunk rotation quat
var chunk_q := Quaternion(godot_q.x, godot_q.z, godot_q.y, godot_q.w)
var rot_vec := Vector4(chunk_q.x, chunk_q.y, chunk_q.z, chunk_q.w)
_core.rotate_object(_selected_id, rot_vec)
property_changed.emit(_selected_id, "rotation", rot_vec)
property_changed.emit(_selected_id, "rotation", godot_q)
func _on_scale_changed(_val: float) -> void:
if _updating or _core == null or _selected_id == "":
return
var new_scale := Vector3(_scale_x.value, _scale_y.value, _scale_z.value)
_core.scale_object(_selected_id, new_scale)
property_changed.emit(_selected_id, "scale", new_scale)
var godot_scale := Vector3(_scale_x.value, _scale_y.value, _scale_z.value)
var chunk_scale := Vector3(godot_scale.x, godot_scale.z, godot_scale.y)
_core.scale_object(_selected_id, chunk_scale)
property_changed.emit(_selected_id, "scale", godot_scale)