extends SceneTree ## Unit-Tests des ScopedVR-Bibliothek-Exports (Karte t_82d2a8dd) — Kern-Logik ## aus addons/modular_builder/scopedvr_export.gd, komplett headless: ## - slugify (Umlaute, Leerzeichen, Zahlen-Anfang, Länge) ## - normalisiere (Fußpunkt y=0, XZ zentriert, Materialien bleiben) ## - trimesh_vertices (Dreiecks-Suppe, Dedup, Quantisierung) ## - glb_bytes (glTF-Magic, Texturen eingebettet, Dreiecke drin) ## Run: godot --headless --path . --script res://tests/test_builder_export.gd const KERN := preload("res://addons/modular_builder/scopedvr_export.gd") var _failures: Array[String] = [] var _checks: int = 0 func _initialize() -> void: _test_slugify() _test_normalisiere() _test_trimesh() _test_glb() print("--------------------------------------------------") print("CHECKS: %d FAILURES: %d" % [_checks, _failures.size()]) for f in _failures: print("FAIL: " + f) quit(0 if _failures.is_empty() else 1) func _check(cond: bool, label: String) -> void: _checks += 1 if cond: print(" ok - " + label) else: _failures.append(label) print(" FAIL - " + label) func _test_slugify() -> void: print("Testgruppe: slugify") _check(KERN.slugify("Bauernhaus rot") == "bauernhaus_rot", "'Bauernhaus rot' → bauernhaus_rot") _check(KERN.slugify("Häuschen Größe 2") == "haeuschen_groesse_2", "Umlaute werden aufgelöst (ist %s)" % KERN.slugify("Häuschen Größe 2")) _check(KERN.slugify("Straße") == "strasse", "'Straße' → strasse") _check(KERN.slugify("2 Häuser") == "haeuser", "führende Ziffern fliegen (ist %s)" % KERN.slugify("2 Häuser")) _check(KERN.slugify(" schon_gut ") == "schon_gut", "Whitespace/Unterstrich-Ränder weg") _check(KERN.slugify("!!!") == "", "nur Sonderzeichen → leer") _check(KERN.slugify("a".repeat(60)).length() == 48, "max. 48 Zeichen") _check(KERN.slugify("Bauernhaus--rot") == "bauernhaus_rot", "Doppel-Unterstrich kollabieren") func _test_normalisiere() -> void: print("Testgruppe: normalisiere") var box := BoxMesh.new() box.size = Vector3(2, 1, 4) var mat := StandardMaterial3D.new() mat.albedo_color = Color(1, 0, 0) var root_mesh := _mesh_aus_bodies([{"mesh": box, "pos": Vector3(10, 5, -20), "mat": mat}]) var norm: Dictionary = KERN.normalisiere(root_mesh) var m: ArrayMesh = norm["mesh"] _check(m != null and m.get_surface_count() == 1, "normalisiere: 1 Oberfläche bleibt") var aabb := m.get_aabb() _check(absf(aabb.position.y) < 0.001, "normalisiere: Boden liegt auf y=0 (min.y=%s)" % aabb.position.y) _check(absf(aabb.get_center().x) < 0.001 and absf(aabb.get_center().z) < 0.001, "normalisiere: XZ zentriert (center=%s)" % aabb.get_center()) _check(aabb.size.is_equal_approx(Vector3(2, 1, 4)), "normalisiere: Größe unverändert") var smat := m.surface_get_material(0) _check(smat != null and (smat as StandardMaterial3D).albedo_color == Color(1, 0, 0), "normalisiere: Material bleibt erhalten") func _test_trimesh() -> void: print("Testgruppe: trimesh_vertices") # Zwei identische Dreiecke + ein echtes: Dedup lässt 3 Vertices-Tripel → 1 übrig. var st := SurfaceTool.new() st.begin(Mesh.PRIMITIVE_TRIANGLES) st.add_vertex(Vector3(0, 0, 0)); st.add_vertex(Vector3(1, 0, 0)); st.add_vertex(Vector3(0, 0, 1)) st.add_vertex(Vector3(0, 0, 0)); st.add_vertex(Vector3(1, 0, 0)); st.add_vertex(Vector3(0, 0, 1)) st.add_vertex(Vector3(0, 1, 0)); st.add_vertex(Vector3(1, 1, 0)); st.add_vertex(Vector3(1, 1, 1)) var mesh := st.commit() var faces := KERN.trimesh_vertices(mesh) _check(faces.size() == 6, "Doppel-Dreieck dedupliziert: 9 → 6 Vertices (ist %d)" % faces.size()) # Entartetes Dreieck (zwei gleiche Punkte) fliegt raus. var st2 := SurfaceTool.new() st2.begin(Mesh.PRIMITIVE_TRIANGLES) st2.add_vertex(Vector3(0, 0, 0)); st2.add_vertex(Vector3(0, 0, 0)); st2.add_vertex(Vector3(1, 0, 0)) var degeneriert := KERN.trimesh_vertices(st2.commit()) _check(degeneriert.is_empty(), "entartetes Dreieck → leer") # Quantisierung: 0,2-mm-Unterschied (weit unter dem 1-mm-Quant) gilt als # identisch — genau auf der Bucket-Grenze (0,5 mm) wäre das nicht garantiert. var st3 := SurfaceTool.new() st3.begin(Mesh.PRIMITIVE_TRIANGLES) st3.add_vertex(Vector3(0, 0, 0)); st3.add_vertex(Vector3(1, 0, 0)); st3.add_vertex(Vector3(0, 0, 1)) st3.add_vertex(Vector3(0, 0.0002, 0)); st3.add_vertex(Vector3(1, 0, 0)); st3.add_vertex(Vector3(0, 0, 1)) _check(KERN.trimesh_vertices(st3.commit()).size() == 3, "Quantisierung 1 mm fasst 0,2-mm-Unterschied zusammen") func _test_glb() -> void: print("Testgruppe: glb_bytes") var box := BoxMesh.new() box.size = Vector3(1, 1, 1) var img := Image.create(8, 8, false, Image.FORMAT_RGB8) img.fill(Color(0.7, 0.3, 0.1)) var mat := StandardMaterial3D.new() mat.albedo_texture = ImageTexture.create_from_image(img) var mesh := _mesh_aus_bodies([{"mesh": box, "pos": Vector3.ZERO, "mat": mat}]) var bytes := KERN.glb_bytes(mesh, "testhaus") _check(KERN.glb_gueltig(bytes), "GLB gültig (Magic + Version + JSON-Chunk, %d Bytes)" % bytes.size()) var json_len := bytes.decode_u32(12) var parsed: Variant = JSON.parse_string(bytes.slice(20, 20 + json_len).get_string_from_utf8()) var gltf: Dictionary = parsed if parsed is Dictionary else {} var tris := 0 for m in gltf.get("meshes", []): for p in m.get("primitives", []): var ai: int = int(p.get("indices", -1)) if ai >= 0: tris += int(gltf["accessors"][ai].get("count", 0)) / 3 _check(tris == 12, "GLB enthält 12 Dreiecke (Box, ist %d)" % tris) _check((gltf.get("images", []) as Array).size() == 1, "Textur ist EINGEBETTET (images=1)") _check(KERN.glb_gueltig("müll".to_utf8_buffer()) == false, "Müll-Bytes werden erkannt") _check(KERN.glb_bytes(null).is_empty(), "null-Mesh → leere Bytes (kein Engine-Error)") ## Hilfsfunktion: baut ein ArrayMesh aus BoxMeshes an Positionen (mit Material). func _mesh_aus_bodies(bodies: Array) -> ArrayMesh: var out := ArrayMesh.new() for b in bodies: var bm: Mesh = b["mesh"] var st := SurfaceTool.new() st.create_from(bm, 0) if b.has("mat"): st.set_material(b["mat"]) var tmp := st.commit() var arrays := tmp.surface_get_arrays(0) var verts: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX] var off: Vector3 = b.get("pos", Vector3.ZERO) for i in verts.size(): verts[i] = verts[i] + off arrays[Mesh.ARRAY_VERTEX] = verts out.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) if b.has("mat"): out.surface_set_material(out.get_surface_count() - 1, b["mat"]) return out