File size: 5,012 Bytes
6343479
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
"""Asset Resolver & Cache Manager (V8/V9).

Implements Sections 47, 48, 90:
- Resolves requested assets by checking cache/database before generation.
- Generates missing assets via image synthesis -> 3D mesh -> FlyAsset compilation.
- Content-addressed hashing avoiding redundant generation.
"""
import os
import json
import hashlib
from typing import Dict, Any, Optional

from src.assets.compiler.types import AssetClass, FlyAsset
from src.assets.compiler.compiler import AssetCompiler
from src.assets.mesh_generator import MeshGenerator


class AssetResolver:
    def __init__(self, cache_dir: str = "assets/compiled", image_cache_dir: str = "visual_evidence/imagined"):
        self.cache_dir = os.path.abspath(cache_dir)
        self.image_cache_dir = os.path.abspath(image_cache_dir)
        os.makedirs(self.cache_dir, exist_ok=True)
        os.makedirs(self.image_cache_dir, exist_ok=True)
        self.compiler = AssetCompiler(output_dir=self.cache_dir)

    def compute_content_id(self, semantic_request: str, params: Dict[str, Any], seed: int = 42) -> str:
        """Section 48: SHA256(semantic_request + model_revision + seed + params)."""
        content = f"{semantic_request}_v9.0_{seed}_{json.dumps(params, sort_keys=True)}"
        return hashlib.sha256(content.encode("utf-8")).hexdigest()[:16]

    def resolve_asset(self,
                      semantic_name: str,
                      category: AssetClass = AssetClass.STRUCTURE,
                      params: Optional[Dict[str, Any]] = None,
                      seed: int = 42,
                      force_regenerate: bool = False) -> Dict[str, Any]:
        params = params or {}
        cid = self.compute_content_id(semantic_name, params, seed=seed)
        target_manifest = os.path.join(self.cache_dir, f"{semantic_name}_{cid}.json")

        # 1. Check Cache (Section 47)
        if not force_regenerate and os.path.exists(target_manifest):
            try:
                with open(target_manifest, "r", encoding="utf-8") as f:
                    manifest = json.load(f)
                return {
                    "status": "CACHE_HIT",
                    "asset_id": manifest.get("asset_id"),
                    "manifest_path": target_manifest,
                    "visual_mesh": manifest.get("visual_mesh"),
                    "dimensions_m": manifest.get("dimensions_m"),
                    "collision": manifest.get("collision"),
                    "category": category.value
                }
            except Exception:
                pass

        # 2. Cache Miss: Generate 3D geometry & FlyAsset package
        mesh_path = os.path.join(self.cache_dir, f"{semantic_name}_{cid}.obj")
        
        if "bridge" in semantic_name.lower():
            span = float(params.get("span_m", 4.0))
            width = float(params.get("width_m", 1.5))
            mesh_data = MeshGenerator.generate_bridge(length_m=span, width_m=width)
            custom_dims = [span, width, 0.18]
        else:
            sx = float(params.get("sx", 2.0))
            sy = float(params.get("sy", 2.0))
            sz = float(params.get("sz", 1.5))
            mesh_data = MeshGenerator.generate_box(sx, sy, sz)
            custom_dims = [sx, sy, sz]

        # Validate mesh
        val = mesh_data.validate()
        if not val.is_valid:
            raise RuntimeError(f"Generated 3D mesh failed validation: {val.errors}")

        # Export OBJ
        mesh_data.export_obj(mesh_path)

        # Optional: generate reference image if image model is available
        img_path = os.path.join(self.image_cache_dir, f"{semantic_name}_{cid}.png")
        try:
            from src.models.image_adapter import LocalImageModel
            img_model = LocalImageModel()
            img_model.generate(f"A detailed realistic {semantic_name}, physical 3D asset",
                               mode="FAST", seed=seed, out_path=img_path)
        except Exception:
            # When image model checkpoint is not present or in lightweight mode, write reference metadata
            with open(img_path + ".meta", "w", encoding="utf-8") as mf:
                json.dump({"semantic": semantic_name, "cid": cid, "mode": "PROCEDURAL_FALLBACK"}, mf)

        # 3. Compile Asset with Scale Normalization & Collision Proxies
        asset = self.compiler.compile_asset(
            semantic_name=semantic_name,
            category=category,
            visual_mesh_path=mesh_path,
            generator_info="FlyBrain_AssetCompiler_v9.0",
            input_hash=cid,
            custom_dimensions=custom_dims,
            explicit_asset_id=f"{semantic_name}_{cid}"
        )

        return {
            "status": "CACHE_MISS_GENERATED",
            "asset_id": asset.asset_id,
            "manifest_path": target_manifest,
            "visual_mesh": mesh_path,
            "dimensions_m": asset.dimensions_m,
            "collision": asset.collision,
            "category": category.value,
            "mesh_validation": val.to_dict()
        }