Mehrfachauswahl gemaessigt (t_bceb0965): Gemeinsames Verschieben aller markierten Objekte relativ — Anker-Objekt fuehrt, Abstaende bleiben exakt erhalten (Probe: 3 Objekte, Delta (+4,-2), Distanzen 6.325/5.831/7.616 vor UND nach). Entf loescht die GESAMTE Auswahl (vorher: nur letztes Objekt), mit einem Undo-Schritt fuer die Gruppe. Undo: 1 Schritt stellt ALLE Objekte gemeinsam wieder her (vorher: 3 Einzelschritte); Redo groupen-basiert. Drehung/Scale-Eingabe bei Mehrfachauswahl: '—' eintippen setzt ALLE auf denselben Wert, Position bleibt relativ. Einzelauswahl unveraendert (Gizmo-Zug ist gizmo-gated und nutzt gemeinsam-ziehend-Pfad nicht). Tests: tests/probe_multi_move.gd neu (12 Checks: Positionen, Abstaende, Undo/Redo, Entf), tests/test_inspector.gd 47/47 gruen (T5 erweitert), test_ui_scale 51/0 gruen, --import 0 Parse-Fehler.
This commit is contained in:
parent
370917f96e
commit
967e4dc54a
4 changed files with 146 additions and 14 deletions
|
|
@ -592,6 +592,16 @@ func _apply(field: String, v: float) -> bool:
|
||||||
var half: Vector2 = _terrain_half()
|
var half: Vector2 = _terrain_half()
|
||||||
var max_y := _max_y()
|
var max_y := _max_y()
|
||||||
var changed := 0
|
var changed := 0
|
||||||
|
# Position-Felder bei Mehrfachauswahl: der eingetippte Wert gilt für das
|
||||||
|
# ERSTE markierte Objekt (Anker), alle anderen wandern mit DEMSELBEN Delta
|
||||||
|
# mit → Abstände bleiben erhalten (Relativ-Move, wie beim Greifen).
|
||||||
|
# Bei 1 Objekt ist das Delta = absoluter Wert → Verhalten unverändert.
|
||||||
|
var rel_delta := 0.0
|
||||||
|
if field == "pos_x" or field == "pos_y" or field == "pos_z":
|
||||||
|
var anchor_pos: Array = (objects[indices[0]] as Dictionary).get("pos", [0.0, 0.0, 0.0])
|
||||||
|
if (anchor_pos as Array).size() == 3:
|
||||||
|
var comp := 0 if field == "pos_x" else (1 if field == "pos_y" else 2)
|
||||||
|
rel_delta = v - float(anchor_pos[comp])
|
||||||
for i in indices:
|
for i in indices:
|
||||||
var obj: Dictionary = objects[i]
|
var obj: Dictionary = objects[i]
|
||||||
var pos: Array = obj.get("pos", [0.0, 0.0, 0.0])
|
var pos: Array = obj.get("pos", [0.0, 0.0, 0.0])
|
||||||
|
|
@ -614,11 +624,11 @@ func _apply(field: String, v: float) -> bool:
|
||||||
changed += 1
|
changed += 1
|
||||||
continue
|
continue
|
||||||
if field == "pos_x":
|
if field == "pos_x":
|
||||||
x = clampf(v, -half.x, half.x)
|
x = clampf(float(pos[0]) + rel_delta, -half.x, half.x)
|
||||||
elif field == "pos_y":
|
elif field == "pos_y":
|
||||||
y = clampf(v, SvmapDocument.terrain_height_at(_main.doc, x, z), max_y)
|
y = clampf(float(pos[1]) + rel_delta, SvmapDocument.terrain_height_at(_main.doc, x, z), max_y)
|
||||||
elif field == "pos_z":
|
elif field == "pos_z":
|
||||||
z = clampf(v, -half.y, half.y)
|
z = clampf(float(pos[2]) + rel_delta, -half.y, half.y)
|
||||||
if absf(x - float(pos[0])) > 0.0001 or absf(y - float(pos[1])) > 0.0001 or absf(z - float(pos[2])) > 0.0001:
|
if absf(x - float(pos[0])) > 0.0001 or absf(y - float(pos[1])) > 0.0001 or absf(z - float(pos[2])) > 0.0001:
|
||||||
obj["pos"] = [x, y, z]
|
obj["pos"] = [x, y, z]
|
||||||
changed += 1
|
changed += 1
|
||||||
|
|
|
||||||
100
tests/probe_multi_move.gd
Normal file
100
tests/probe_multi_move.gd
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
extends SceneTree
|
||||||
|
## Beweis-Probe (Karte t_bceb0965, WEGWERF — nicht Teil der Suite):
|
||||||
|
## Mehrfachauswahl mit 3 Objekten — gemeinsames Verschieben via Inspektor
|
||||||
|
## (relativ: Abstände bleiben), Zahlen vor/nach, EIN Undo stellt ALLE her,
|
||||||
|
## abschließend ein PNG mit den 3 markierten Objekten nach dem Verschieben.
|
||||||
|
## Aufruf: xvfb-run -a godot4 --path . --rendering-driver opengl3 \
|
||||||
|
## -s res://tests/probe_multi_move.gd (PROBE_PNG = Ausgabepfad)
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
_run.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
|
func _pos(obj: Dictionary) -> Array:
|
||||||
|
return obj.get("pos", [0.0, 0.0, 0.0])
|
||||||
|
|
||||||
|
|
||||||
|
func _dist(a: Dictionary, b: Dictionary) -> float:
|
||||||
|
var pa := _pos(a)
|
||||||
|
var pb := _pos(b)
|
||||||
|
return Vector2(float(pa[0]), float(pa[2])).distance_to(Vector2(float(pb[0]), float(pb[2])))
|
||||||
|
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
var scene: PackedScene = load("res://scenes/main.tscn")
|
||||||
|
var main = scene.instantiate()
|
||||||
|
root.add_child(main)
|
||||||
|
await process_frame
|
||||||
|
await process_frame
|
||||||
|
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
|
||||||
|
DisplayServer.window_set_size(Vector2i(2328, 1310))
|
||||||
|
|
||||||
|
# Drei Objekte im Dreieck (verschiedene X/Z → Abstände sind eindeutig).
|
||||||
|
var objs: Array = main.doc["objects"]
|
||||||
|
objs.append({"id": "barrel_001", "pos": [8.0, 0.0, 8.0], "scale": 1.5, "rot_y_deg": 30.0})
|
||||||
|
objs.append({"id": "crate_wood", "pos": [14.0, 0.0, 6.0], "scale": 1.0, "rot_y_deg": 0.0})
|
||||||
|
objs.append({"id": "wall_001", "pos": [11.0, 0.0, 13.0], "scale": 1.0, "rot_y_deg": 0.0})
|
||||||
|
main.call("_after_structure_change")
|
||||||
|
main.history.clear()
|
||||||
|
main.interaction.call("select_many", [0, 1, 2])
|
||||||
|
await process_frame
|
||||||
|
|
||||||
|
var cam = main.get("camera")
|
||||||
|
if cam != null:
|
||||||
|
cam.call("focus_on", Vector3(11.0, 0.0, 9.5))
|
||||||
|
cam.set("distance", 34.0)
|
||||||
|
|
||||||
|
# ---- Zahlen VORHER ----
|
||||||
|
var d01_vor := _dist(objs[0], objs[1])
|
||||||
|
var d02_vor := _dist(objs[0], objs[2])
|
||||||
|
var d12_vor := _dist(objs[1], objs[2])
|
||||||
|
print("PROBE_VOR pos0=%s pos1=%s pos2=%s" % [_pos(objs[0]), _pos(objs[1]), _pos(objs[2])])
|
||||||
|
print("PROBE_VOR Abstaende: 0-1=%.3f 0-2=%.3f 1-2=%.3f" % [d01_vor, d02_vor, d12_vor])
|
||||||
|
|
||||||
|
# ---- Gemeinsames Verschieben: Inspektor pos_x tippen (Anker → 12.0) ----
|
||||||
|
var insp = main.get("inspector")
|
||||||
|
var w = insp.widget("pos_x")
|
||||||
|
w.edit.text = "12.0"
|
||||||
|
w.edit.text_submitted.emit("12.0")
|
||||||
|
await process_frame
|
||||||
|
|
||||||
|
var d01_nach := _dist(objs[0], objs[1])
|
||||||
|
var d02_nach := _dist(objs[0], objs[2])
|
||||||
|
var d12_nach := _dist(objs[1], objs[2])
|
||||||
|
print("PROBE_NACH pos0=%s pos1=%s pos2=%s" % [_pos(objs[0]), _pos(objs[1]), _pos(objs[2])])
|
||||||
|
print("PROBE_NACH Abstaende: 0-1=%.3f 0-2=%.3f 1-2=%.3f" % [d01_nach, d02_nach, d12_nach])
|
||||||
|
var ok_abst := absf(d01_nach - d01_vor) < 0.001 \
|
||||||
|
and absf(d02_nach - d02_vor) < 0.001 \
|
||||||
|
and absf(d12_nach - d12_vor) < 0.001
|
||||||
|
print("PROBE_ABSTAENDE_ERHALTEN %s" % ("OK" if ok_abst else "FAIL"))
|
||||||
|
|
||||||
|
# ---- EIN Undo stellt ALLE drei her ----
|
||||||
|
main.call("_do_undo")
|
||||||
|
await process_frame
|
||||||
|
var p0_undo := _pos(objs[0])
|
||||||
|
var p1_undo := _pos(objs[1])
|
||||||
|
var p2_undo := _pos(objs[2])
|
||||||
|
var ok_undo := str(p0_undo) == "8, 0, 8" or (absf(float(p0_undo[0]) - 8.0) < 0.001 \
|
||||||
|
and absf(float(p0_undo[2]) - 8.0) < 0.001)
|
||||||
|
var ok_undo1 := absf(float(p1_undo[0]) - 14.0) < 0.001 and absf(float(p1_undo[2]) - 6.0) < 0.001
|
||||||
|
var ok_undo2 := absf(float(p2_undo[0]) - 11.0) < 0.001 and absf(float(p2_undo[2]) - 13.0) < 0.001
|
||||||
|
print("PROBE_UNDO pos0=%s pos1=%s pos2=%s" % [p0_undo, p1_undo, p2_undo])
|
||||||
|
print("PROBE_UNDO_ALLE_ZURUECK %s" % ("OK" if (ok_undo and ok_undo1 and ok_undo2) else "FAIL"))
|
||||||
|
|
||||||
|
# ---- Zustand für das Bild wiederherstellen: Redo = dieselbe Verschiebung ----
|
||||||
|
main.call("_do_redo")
|
||||||
|
await process_frame
|
||||||
|
print("PROBE_REDO pos0=%s pos1=%s pos2=%s" % [_pos(objs[0]), _pos(objs[1]), _pos(objs[2])])
|
||||||
|
|
||||||
|
# ---- PNG: drei markierte Objekte nach der gemeinsamen Verschiebung ----
|
||||||
|
for i in 6:
|
||||||
|
await process_frame
|
||||||
|
await RenderingServer.frame_post_draw
|
||||||
|
var img := root.get_texture().get_image()
|
||||||
|
var out := OS.get_environment("PROBE_PNG")
|
||||||
|
if out.is_empty():
|
||||||
|
out = "/tmp/probe_multi_move.png"
|
||||||
|
img.save_png(out)
|
||||||
|
print("PNG_OK ", out)
|
||||||
|
quit(0)
|
||||||
1
tests/probe_multi_move.gd.uid
Normal file
1
tests/probe_multi_move.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://r4phip53o7fv
|
||||||
|
|
@ -68,14 +68,17 @@ func _run_all() -> void:
|
||||||
await process_frame
|
await process_frame
|
||||||
glb_ok = load("res://src/viewport/glb_mesh_store.gd").mesh_fuer("barrel_001") != null
|
glb_ok = load("res://src/viewport/glb_mesh_store.gd").mesh_fuer("barrel_001") != null
|
||||||
|
|
||||||
_t1_einzelauswahl()
|
# await PFLICHT: T4/T5/T8 enthalten process_frame-Awaits — ohne await
|
||||||
_t2_tippen()
|
# lief _finish() sofort durch und die Mehrfach-Checks liefen NIEMALS
|
||||||
_t3_ziehen()
|
# (Suite zeigte „ALL OK: 33 checks" und skippte still T4/T5/T8).
|
||||||
_t4_mehrfach_drehung()
|
await _t1_einzelauswahl()
|
||||||
_t5_mixed_angleichen()
|
await _t2_tippen()
|
||||||
_t6_groesse()
|
await _t3_ziehen()
|
||||||
_t7_auf_boden()
|
await _t4_mehrfach_drehung()
|
||||||
_t8_leer()
|
await _t5_mixed_angleichen()
|
||||||
|
await _t6_groesse()
|
||||||
|
await _t7_auf_boden()
|
||||||
|
await _t8_leer()
|
||||||
_finish()
|
_finish()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -164,13 +167,31 @@ func _t5_mixed_angleichen() -> void:
|
||||||
await process_frame
|
await process_frame
|
||||||
var w = insp.widget("pos_x")
|
var w = insp.widget("pos_x")
|
||||||
_check(str(w.edit.text) == "—", "T5 ungleiche X-Werte zeigen „—“")
|
_check(str(w.edit.text) == "—", "T5 ungleiche X-Werte zeigen „—“")
|
||||||
|
# Karte t_bceb0965: Position bleibt RELATIV — der getippte Wert gilt für
|
||||||
|
# den Anker (erstes markiertes Objekt), die anderen wandern mit demselben
|
||||||
|
# Delta mit (Abstände bleiben). Vorher: x0=4.0, x1=-6.0 → Abstand 10.0.
|
||||||
|
var x0_vor: float = float(_sel_obj(0).get("pos", [0, 0, 0])[0])
|
||||||
|
var x1_vor: float = float(_sel_obj(1).get("pos", [0, 0, 0])[0])
|
||||||
w.edit.text = "3"
|
w.edit.text = "3"
|
||||||
w.edit.text_submitted.emit("3")
|
w.edit.text_submitted.emit("3")
|
||||||
var x0: float = float(_sel_obj(0).get("pos", [0, 0, 0])[0])
|
var x0: float = float(_sel_obj(0).get("pos", [0, 0, 0])[0])
|
||||||
var x1: float = float(_sel_obj(1).get("pos", [0, 0, 0])[0])
|
var x1: float = float(_sel_obj(1).get("pos", [0, 0, 0])[0])
|
||||||
_check(absf(x0 - 3.0) < 0.0001 and absf(x1 - 3.0) < 0.0001,
|
_check(absf(x0 - 3.0) < 0.0001, "T5 Anker (Objekt 0) landet auf 3.0")
|
||||||
"T5 Tippen gleicht beide X auf 3.0 an")
|
_check(absf((x1 - x0) - (x1_vor - x0_vor)) < 0.0001,
|
||||||
_check(_undo_steps_to_clear() == 1, "T5 Angleichen = GENAU EIN Undo-Schritt")
|
"T5 Relativ: Abstand zwischen beiden bleibt (%.1f m)" % absf(x1 - x0))
|
||||||
|
_check(_undo_steps_to_clear() == 1, "T5 Relativ-Move = GENAU EIN Undo-Schritt")
|
||||||
|
_check(absf(float(_sel_obj(0).get("pos", [0, 0, 0])[0]) - x0_vor) < 0.0001
|
||||||
|
and absf(float(_sel_obj(1).get("pos", [0, 0, 0])[0]) - x1_vor) < 0.0001,
|
||||||
|
"T5 Undo stellt beide Ausgangs-X wieder her")
|
||||||
|
# Drehung/Größe bleiben ABSOLUT bei Mehrfachauswahl (Karten-Auftrag):
|
||||||
|
main.interaction.call("select_many", [0, 1])
|
||||||
|
await process_frame
|
||||||
|
var wr = insp.widget("rot_y")
|
||||||
|
wr.edit.text = "45"
|
||||||
|
wr.edit.text_submitted.emit("45")
|
||||||
|
var r1: float = float(_sel_obj(1).get("rot_y_deg", 0.0))
|
||||||
|
_check(absf(r1 - 45.0) < 0.0001, "T5 Drehung bei Mehrfachauswahl = absolut (beide 45°)")
|
||||||
|
_undo_steps_to_clear()
|
||||||
|
|
||||||
|
|
||||||
func _t6_groesse() -> void:
|
func _t6_groesse() -> void:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue