EG/project/project_schema.py
2026-03-19 11:06:17 +08:00

220 lines
5.6 KiB
Python

"""Project layout and schema helpers for MetaCore project v2."""
from __future__ import annotations
import os
import uuid
from dataclasses import dataclass
ENGINE_NAME = "MetaCore"
PROJECT_SCHEMA_VERSION = 2
ASSET_DB_SCHEMA_VERSION = 1
METASCENE_SCHEMA_VERSION = 2
RUNTIME_SCENE_SCHEMA_VERSION = 2
MANIFEST_SCHEMA_VERSION = 1
SCENE_DESCRIPTION_EXTENSION = ".metascene"
MODEL_EXTENSIONS = {
".bam",
".egg",
".obj",
".fbx",
".gltf",
".glb",
".ply",
".stl",
}
TEXTURE_EXTENSIONS = {
".png",
".jpg",
".jpeg",
".bmp",
".tga",
".tif",
".tiff",
".dds",
".hdr",
".exr",
}
AUDIO_EXTENSIONS = {".wav", ".mp3", ".ogg", ".flac"}
VIDEO_EXTENSIONS = {".mp4", ".avi", ".mov", ".mkv", ".wmv", ".webm"}
SCRIPT_EXTENSIONS = {".py", ".pyc"}
UI_EXTENSIONS = {".json", ".ttf", ".otf", ".woff", ".woff2"}
def generate_guid() -> str:
return uuid.uuid4().hex
def normalize_path(path: str) -> str:
return os.path.normpath(os.path.abspath(path))
def relative_project_path(project_root: str, target_path: str) -> str:
project_root = normalize_path(project_root)
target_path = normalize_path(target_path)
try:
relative_path = os.path.relpath(target_path, project_root)
except ValueError:
return ""
if relative_path.startswith(".."):
return ""
return relative_path.replace("\\", "/")
def detect_asset_type(file_path: str) -> str:
extension = os.path.splitext(str(file_path or ""))[1].lower()
if extension in MODEL_EXTENSIONS:
return "model"
if extension in TEXTURE_EXTENSIONS:
return "texture"
if extension in AUDIO_EXTENSIONS:
return "audio"
if extension in VIDEO_EXTENSIONS:
return "video"
if extension in SCRIPT_EXTENSIONS:
return "script"
if extension in UI_EXTENSIONS:
return "ui"
return "other"
def get_asset_subdir_for_type(asset_type: str) -> str:
return {
"model": "Models",
"texture": "Textures",
"audio": "Audio",
"video": "Video",
"ui": "UI",
"script": "Scripts",
"other": "Misc",
}.get(asset_type, "Misc")
def default_build_settings() -> dict:
return {
"enabled_scene_guids": [],
"output_format": "directory",
"windows_player": True,
"active_profile": "default",
"profiles": {
"default": {
"name": "Default",
"target_platform": "windows",
"output_format": "directory",
"output_dir": "Builds",
"windows": {
"enabled": True,
"exe_name": "",
"windowed": True,
},
"runtime": {
"startup_scene_guid": "",
"enabled_scene_guids": [],
"script_mode": "pyc",
},
}
},
}
@dataclass(frozen=True)
class ProjectLayout:
project_root: str
def __post_init__(self):
object.__setattr__(self, "project_root", normalize_path(self.project_root))
@property
def project_file(self) -> str:
return os.path.join(self.project_root, "project.json")
@property
def assets_root(self) -> str:
return os.path.join(self.project_root, "Assets")
@property
def scenes_root(self) -> str:
return os.path.join(self.project_root, "Scenes")
@property
def library_root(self) -> str:
return os.path.join(self.project_root, "Library")
@property
def builds_root(self) -> str:
return os.path.join(self.project_root, "Builds")
@property
def asset_db_path(self) -> str:
return os.path.join(self.library_root, "AssetDB.json")
@property
def imported_root(self) -> str:
return os.path.join(self.library_root, "Imported")
@property
def build_cache_root(self) -> str:
return os.path.join(self.library_root, "BuildCache")
@property
def scripts_root(self) -> str:
return os.path.join(self.assets_root, "Scripts")
@property
def models_root(self) -> str:
return os.path.join(self.assets_root, "Models")
@property
def textures_root(self) -> str:
return os.path.join(self.assets_root, "Textures")
@property
def audio_root(self) -> str:
return os.path.join(self.assets_root, "Audio")
@property
def video_root(self) -> str:
return os.path.join(self.assets_root, "Video")
@property
def ui_root(self) -> str:
return os.path.join(self.assets_root, "UI")
@property
def misc_root(self) -> str:
return os.path.join(self.assets_root, "Misc")
def meta_path_for_asset(self, asset_abs_path: str) -> str:
return f"{normalize_path(asset_abs_path)}.meta"
def imported_asset_dir(self, asset_guid: str) -> str:
return os.path.join(self.imported_root, asset_guid)
def build_cache_dir(self, scene_guid: str) -> str:
return os.path.join(self.build_cache_root, scene_guid)
def scene_file(self, scene_name: str) -> str:
return os.path.join(self.scenes_root, f"{scene_name}{SCENE_DESCRIPTION_EXTENSION}")
def ensure_project_directories(layout: ProjectLayout) -> None:
for path in (
layout.assets_root,
layout.models_root,
layout.textures_root,
layout.audio_root,
layout.video_root,
layout.ui_root,
layout.scripts_root,
layout.misc_root,
layout.scenes_root,
layout.library_root,
layout.imported_root,
layout.build_cache_root,
layout.builds_root,
):
os.makedirs(path, exist_ok=True)