diff --git a/docs/port/U2-WORLD.md b/docs/port/U2-WORLD.md index f5f4aeb..9044b1d 100644 --- a/docs/port/U2-WORLD.md +++ b/docs/port/U2-WORLD.md @@ -213,6 +213,14 @@ Behandlung: - Nach den Abstürzen fragte macOS beim nächsten Start, ob die Fenster wiederhergestellt werden sollen, und wartete auf einen Klick. Der Benchmark startet deshalb mit `-ApplePersistenceIgnoreState YES`. Das gilt nur für diesen Start und ändert keine Einstellung. - Dauerhaft hilft nur ein Projektort außerhalb von iCloud Drive. Deshalb zieht der Arbeitsordner nach `~/Developer/TowerDefense` (Entscheidung vom 1. Oktober 2026, [PORT-STATUS.md](PORT-STATUS.md)). +#### Befund nach dem Umzug: absoluter Pfad im FBX-Kopf + +Blenders FBX-Schreiber trägt den absoluten Pfad der geöffneten `.blend`-Datei in den Dateikopf ein (`ApplicationNativeFile`). Deshalb enthalten 83 der 86 FBX-Dateien aus Exporter-Version 10 `/Users/martin/Documents/…`, und ihre Bytes hängen vom Ort des Checkouts ab. Die byteidentische Reproduzierbarkeit galt damit nur am selben Pfad; nach dem Umzug nach `~/Developer` gilt sie für die eingecheckten Dateien nicht mehr. + +- **Korrektur:** Ab Exporter-Version 11 steht dort der relative Quellpfad aus dem Manifest, bei Generatoren nichts. +- **Geprüft an der Grenzwacht:** Zwei Läufe sind byteidentisch, es steht kein absoluter Pfad mehr in der Datei, und das Prüfprotokoll ist bis auf Version und Dateigröße gleich (64 Byte kürzer, genau der weggefallene Pfadanfang). +- **Eingecheckte Dateien bleiben Version 10** bis zum nächsten Gesamtexport, spätestens in U5, das alle Modelle neu erzeugt. Ein Neuexport aller Dateien nur für diesen Kopfeintrag hätte das Repository um rund 140 MB vergrößert. Geometrie, Skelette, Clips und Materialien sind nicht betroffen. + #### Leistung mit allen Modellen Messung wie in U2D (macOS-Player, Apple M5, 1920 × 1080 im Fenster, VSync aus, Seed 9 ab Runde 21), drei Läufe: diff --git a/tools/blender/lune_fbx_export.py b/tools/blender/lune_fbx_export.py index 31e7faa..97e25d5 100644 --- a/tools/blender/lune_fbx_export.py +++ b/tools/blender/lune_fbx_export.py @@ -27,7 +27,7 @@ from pathlib import Path from mathutils import Matrix, Vector UNITY_ROOT = Path(__file__).resolve().parents[2] -EXPORTER_VERSION = 10 +EXPORTER_VERSION = 11 # Tangents are not written: Unity computes them with MikkTSpace like Blender (import rule # CalculateMikk); per-corner tangents and binormals in double precision doubled the file size. @@ -385,10 +385,18 @@ def run_checks(spec, report, before, clips, expect): report.check('forward_marker_unity_plus_z', marker is not None and marker[2] > 0.1 and abs(marker[0]) < 1e-3, marker) +# Source file named in the FBX header (ApplicationNativeFile): the manifest path relative to the +# reference repository, set per asset; generators have no saved file. +NATIVE_FILE = {'path': ''} + + def reproducible_fbx(): """Byte-identical FBX for identical sources: Blender's FBX writer stamps the current time into - the header and derives object ids from Python's string hash. The clock is fixed here; the hash - seed comes from PYTHONHASHSEED=0 with --python-use-system-env (tools/blender/export_fbx.sh).""" + the header, writes the absolute path of the open .blend file (ApplicationNativeFile) and derives + object ids from Python's string hash. The clock is fixed and the path is the manifest's relative + source path here (since exporter version 11; version 10 wrote the absolute path, so its files + depend on the checkout location); the hash seed comes from PYTHONHASHSEED=0 with + --python-use-system-env (tools/blender/export_fbx.sh).""" import datetime import io_scene_fbx.export_fbx_bin as writer @@ -402,6 +410,23 @@ def reproducible_fbx(): writer.datetime = Clock + class Data: + """bpy.data with a neutral filepath; everything else is the real data of the open file.""" + @property + def filepath(self): + return NATIVE_FILE['path'] + + def __getattr__(self, name): + return getattr(bpy.data, name) + + class Bpy: + data = Data() + + def __getattr__(self, name): + return getattr(bpy, name) + + writer.bpy = Bpy() + def export_fbx(path, objects): bpy.ops.object.select_all(action='DESELECT') @@ -474,8 +499,10 @@ def export_asset(spec, godot_root): source = godot_root / spec['source'] if spec.get('generator'): run_generator(spec, godot_root, source) + NATIVE_FILE['path'] = '' else: bpy.ops.wm.open_mainfile(filepath=str(source)) + NATIVE_FILE['path'] = spec['source'] report.data['source_sha256'] = hashlib.sha256(source.read_bytes()).hexdigest() copy_textures(spec, godot_root, report) expect = godot_expectations(spec, godot_root)