extends RefCounted class_name BauOps ## Dokument-Operationen des Bauen-Modus (pure Funktionen, headless testbar). ## ## Ein Gebäude = eine Gruppe (GroupOps, Feld "group" + group_names) aus `bau_*`- ## Objekten. Alles Weitere — Verschieben, Drehen, Kopieren, Löschen, Symmetrie, ## Speichern — läuft über die normalen Objekt-Funktionen. Dieser Teil kennt nur, ## was speziell am Bauen ist: Teil setzen/ersetzen, Gebäude-Gruppen, Migration ## der alten "gebaeude"-Sektion (Karte t_97ea59c5 → Umbau 25.10.). static func teile_zahl(doc: Dictionary) -> int: var n := 0 for o in (doc.get("objects", []) as Array): if o is Dictionary and BauKatalog.ist_bau(str((o as Dictionary).get("id", ""))): n += 1 return n ## Dreiecke aller Bauteile (grobe Poly-Übersicht für die Statuszeile). static func dreiecke_gesamt(doc: Dictionary) -> int: var n := 0 for o in (doc.get("objects", []) as Array): if o is Dictionary: n += BauKatalog.dreiecke(str((o as Dictionary).get("id", ""))) return n ## Neue Gebäude-Gruppe anlegen und benennen. Rückgabe: Gruppen-ID. static func gruppe_neu(doc: Dictionary) -> int: var gid := GroupOps.next_group_id(doc) GroupState.set_group_name(doc, gid, "Gebäude %d" % (gebaeude_zahl(doc) + 1)) return gid ## Anzahl Gebäude-Gruppen (Gruppen, deren erstes Mitglied ein Bauteil ist). static func gebaeude_zahl(doc: Dictionary) -> int: return gebaeude_gruppen(doc).size() ## [{gid, name, indizes, dreiecke}] für alle Gebäude-Gruppen. static func gebaeude_gruppen(doc: Dictionary) -> Array: var out: Array = [] var objects: Array = doc.get("objects", []) for gid in GroupOps.active_group_ids(doc): var members := GroupOps.members_of(doc, int(gid)) if members.is_empty(): continue var bau := false var tris := 0 for i in members: var oid := str((objects[i] as Dictionary).get("id", "")) if BauKatalog.ist_bau(oid): bau = true tris += BauKatalog.dreiecke(oid) if bau: out.append({"gid": int(gid), "name": GroupState.display_name(doc, int(gid)), "indizes": members, "dreiecke": tris}) return out ## Teil setzen oder ersetzen (snap = BuildSnap.berechne-Ergebnis, ok=true). ## Mutiert doc["objects"]; der Aufrufer macht snapshot/commit. ## gid: Zielgruppe (Gebäude); bei Symmetrie bekommt der Zwilling ein eigenes ## Spiegel-Gebäude (spiegel_gid, vom Aufrufer gemerkt: 1 Original-Gebäude ↔ 1 Spiegel). ## Rückgabe: {index, twin, ersetzt}. static func setze_teil(doc: Dictionary, oid: String, snap: Dictionary, gid: int, sym_mode: int, spiegel_gid := -1) -> Dictionary: var objects: Array = doc["objects"] var pos: Vector3 = snap["pos"] var rot := float(snap["rot"]) var ersetze := int(snap.get("ersetze", -1)) if ersetze >= 0 and ersetze < objects.size(): var o: Dictionary = objects[ersetze] o["id"] = oid o["pos"] = [pos.x, pos.y, pos.z] SvmapIO.rot_setzen(o, Vector3(0.0, rot, 0.0)) # Symmetrie: der Zwilling bekommt dasselbe Teil an seiner (gespiegelten) Stelle. var key := SymmetryCore.sym_of(o) if sym_mode != SymmetryCore.Mode.OFF and not key.is_empty(): var partner := SymmetryCore.find_partner_index(objects, key, ersetze) if partner >= 0 and objects[partner] is Dictionary: (objects[partner] as Dictionary)["id"] = oid return {"index": ersetze, "twin": -1, "ersetzt": true} var neu := {"id": oid, "pos": [pos.x, pos.y, pos.z], "scale": 1.0, "rot_y_deg": rot, "group": gid} objects.append(neu) var index := objects.size() - 1 var twin := -1 if sym_mode != SymmetryCore.Mode.OFF: var res: Dictionary = ObjectOps.symmetry_place_twin(doc, index, sym_mode) twin = int(res.get("twin", -1)) if twin >= 0 and spiegel_gid >= 0: (objects[twin] as Dictionary)["group"] = spiegel_gid return {"index": index, "twin": twin, "ersetzt": false} ## Eine ganze Gebäude-Gruppe als Indizes (für „Ganzes Gebäude wählen“). static func gebaeude_von(doc: Dictionary, index: int) -> Array: var objects: Array = doc.get("objects", []) if index < 0 or index >= objects.size(): return [] var gid := GroupOps.group_id(objects[index]) if gid == GroupOps.UNGROUPED: return [index] return GroupOps.members_of(doc, gid) # ----------------------------------------------------------- Demo / Test ---- ## Kleines Haus über die echte Klick-Logik setzen (Fundament, Wände, Tür, Fenster, ## Giebel, Dach, Treppe) — für Demo und Tests. Jeder Schritt = BuildSnap-Treffer ## wie im Editor. Rückgabe: Gruppen-ID. history kann null sein. static func haus_demo(doc: Dictionary, history: Variant, mitte: Vector3) -> int: var gid := gruppe_neu(doc) var terrain := func(_x: float, _z: float) -> float: return mitte.y var before: Dictionary = history.snapshot(["objects", "group_names"]) if history != null else {} # 2×2 Zellen (4×4 m): Zellmitten bei (±1, ±1) relativ zur Mitte. var ox := floorf(mitte.x / 2.0) * 2.0 var oz := floorf(mitte.z / 2.0) * 2.0 var schritte := [ ["bau_fundament", Vector3(ox - 1, mitte.y, oz - 1), 0.0], ["bau_fundament", Vector3(ox + 1, mitte.y, oz - 1), 0.0], ["bau_fundament", Vector3(ox - 1, mitte.y, oz + 1), 0.0], ["bau_fundament", Vector3(ox + 1, mitte.y, oz + 1), 0.0], # Wände liegen GANZ in der Zelle, in der die Maus ist (Außenseite bündig mit # der Kante) — deshalb zielen die Klicks 10 cm ins Hausinnere. ["bau_wand", Vector3(ox - 1, mitte.y, oz - 1.9), 0.0], ["bau_wand_fenster", Vector3(ox + 1, mitte.y, oz - 1.9), 0.0], ["bau_wand_tuer", Vector3(ox - 1, mitte.y, oz + 1.9), 0.0], ["bau_wand", Vector3(ox + 1, mitte.y, oz + 1.9), 0.0], ["bau_wand", Vector3(ox - 1.9, mitte.y, oz - 1), 90.0], ["bau_wand_scharte", Vector3(ox - 1.9, mitte.y, oz + 1), 90.0], ["bau_wand", Vector3(ox + 1.9, mitte.y, oz - 1), 90.0], ["bau_wand", Vector3(ox + 1.9, mitte.y, oz + 1), 90.0], ] for st in schritte: _demo_setzen(doc, str(st[0]), st[1] as Vector3, float(st[2]), gid, terrain) # Ein großes Satteldach (4×4 m) auf die Wandoberkante. var oben := mitte.y + BauKatalog.WAND_H _demo_setzen(doc, "bau_dach_giebel_4", Vector3(ox, oben, oz), 0.0, gid, terrain) if history != null: history.commit("Haus gebaut (Demo)", before, history.snapshot(["objects", "group_names"])) return gid static func _demo_setzen(doc: Dictionary, oid: String, hit: Vector3, dreh: float, gid: int, gelaende: Callable) -> void: var teile := BuildSnap.sammle_teile(doc) var snap := BuildSnap.berechne(oid, hit, dreh, teile, gelaende) if bool(snap["ok"]): setze_teil(doc, oid, snap, gid, SymmetryCore.Mode.OFF) # --------------------------------------------------------------- Migration -- ## Alte Sektion doc["gebaeude"] (Karte t_97ea59c5, 10 prozedurale Teile ohne ## Registry) → bau_*-Objekte in EINER Gruppe, dann Sektion entfernen. Beste ## Näherung: Teil-Ursprünge der alten Teile lagen an Wandanfang/Zellmitte, die ## neuen Bauteile an der Mitte der Grundfläche; Höhen (2 m → 3 m) ändern sich. ## Rückgabe: Anzahl übernommener Teile. const _ALT := { "fundament": "bau_fundament", "boden": "bau_boden", "wand": "bau_wand", "tuer": "bau_wand_tuer", "fenster": "bau_wand_fenster", "giebelwand": "bau_giebelwand", "giebelwand_rechts": "bau_giebelwand", "dach_giebel": "bau_dach_giebel", "dach_pyramide": "bau_dach_pyramide", "treppe": "bau_treppe", } static func migriere_gebaeude(doc: Dictionary) -> int: if not doc.has("gebaeude"): return 0 var alt: Variant = doc["gebaeude"] doc.erase("gebaeude") if not (alt is Array) or (alt as Array).is_empty(): return 0 if not doc.has("objects") or not (doc["objects"] is Array): doc["objects"] = [] var objects: Array = doc["objects"] var gid := gruppe_neu(doc) var n := 0 for teil in alt: if not (teil is Dictionary): continue var oid: String = _ALT.get(str((teil as Dictionary).get("id", "")), "") if oid.is_empty(): continue var p: Array = (teil as Dictionary).get("pos", [0.0, 0.0, 0.0]) var rot := float((teil as Dictionary).get("rot", 0.0)) var pos := Vector3(float(p[0]), float(p[1]), float(p[2])) var local := Vector3.ZERO match str((teil as Dictionary).get("id", "")): "wand", "tuer", "fenster", "giebelwand", "giebelwand_rechts": local = Vector3(1.0, 0.0, 0.0) # alt: Ursprung am Wandanfang "treppe": local = Vector3(2.0, 0.0, 0.0) # alt: 2 m lang ab Ursprung "boden": pos.y -= BauKatalog.DICKE_M # alt: Oberkante am Ursprung pos += Basis(Vector3.UP, deg_to_rad(rot)) * local objects.append({"id": oid, "pos": [pos.x, pos.y, pos.z], "scale": 1.0, "rot_y_deg": fposmod(rot, 360.0), "group": gid}) n += 1 if n == 0: GroupState.set_group_name(doc, gid, "") return n