- move assets (raw jsons, icons, meshes and i18n data) into the root of repo. - move script into the root of repo. - modify blender manifest according to this changes. - optimize gitignore.
176 lines
5.4 KiB
Python
176 lines
5.4 KiB
Python
import enum
|
|
import json
|
|
import logging
|
|
import ast
|
|
from typing import Optional, Self
|
|
from pydantic import BaseModel, RootModel, Field, model_validator, ValidationError
|
|
import common
|
|
|
|
|
|
def validate_programmable_str(probe: str) -> None:
|
|
try:
|
|
ast.parse(probe)
|
|
except SyntaxError:
|
|
raise ValueError(
|
|
f'String {probe} may not be a valid Python statement which is not suit for programmable field.')
|
|
|
|
|
|
class ShowcaseType(enum.StrEnum):
|
|
Nothing = 'none'
|
|
Floor = 'floor'
|
|
Rail = 'Rail'
|
|
Wood = 'wood'
|
|
|
|
|
|
class ShowcaseCfgType(enum.StrEnum):
|
|
Float = 'float'
|
|
Int = 'int'
|
|
Bool = 'bool'
|
|
Face = 'face'
|
|
|
|
|
|
class ShowcaseCfg(BaseModel):
|
|
field: str = Field(frozen=True, strict=True)
|
|
type: ShowcaseCfgType = Field(frozen=True)
|
|
title: str = Field(frozen=True, strict=True)
|
|
desc: str = Field(frozen=True, strict=True)
|
|
default: str = Field(frozen=True, strict=True)
|
|
|
|
@model_validator(mode='after')
|
|
def verify_prog_field(self) -> Self:
|
|
validate_programmable_str(self.default)
|
|
return self
|
|
|
|
|
|
class Showcase(BaseModel):
|
|
title: str = Field(frozen=True, strict=True)
|
|
icon: str = Field(frozen=True, strict=True)
|
|
type: ShowcaseType = Field(frozen=True)
|
|
cfgs: list[ShowcaseCfg] = Field(frozen=True, strict=True)
|
|
|
|
|
|
class Param(BaseModel):
|
|
field: str = Field(frozen=True, strict=True)
|
|
data: str = Field(frozen=True, strict=True)
|
|
|
|
@model_validator(mode='after')
|
|
def verify_prog_field(self) -> Self:
|
|
validate_programmable_str(self.data)
|
|
return self
|
|
|
|
|
|
class Var(BaseModel):
|
|
field: str = Field(frozen=True, strict=True)
|
|
data: str = Field(frozen=True, strict=True)
|
|
|
|
@model_validator(mode='after')
|
|
def verify_prog_field(self) -> Self:
|
|
validate_programmable_str(self.data)
|
|
return self
|
|
|
|
|
|
class Vertex(BaseModel):
|
|
skip: str = Field(frozen=True, strict=True)
|
|
data: str = Field(frozen=True, strict=True)
|
|
|
|
@model_validator(mode='after')
|
|
def verify_prog_field(self) -> Self:
|
|
validate_programmable_str(self.skip)
|
|
validate_programmable_str(self.data)
|
|
return self
|
|
|
|
|
|
class Face(BaseModel):
|
|
skip: str = Field(frozen=True, strict=True)
|
|
texture: str = Field(frozen=True, strict=True)
|
|
indices: list[int] = Field(frozen=True, strict=True)
|
|
uvs: list[str] = Field(frozen=True, strict=True)
|
|
normals: Optional[list[str]] = Field(frozen=True, strict=True)
|
|
|
|
@model_validator(mode='after')
|
|
def verify_count(self) -> Self:
|
|
expected_count = len(self.indices)
|
|
if len(self.uvs) != expected_count:
|
|
raise ValueError('The length of uv array is not matched with indices.')
|
|
if (self.normals is not None) and (len(self.normals) != expected_count):
|
|
raise ValueError('The length of normal array is not matched with indices.')
|
|
return self
|
|
|
|
@model_validator(mode='after')
|
|
def verify_prog_field(self) -> Self:
|
|
validate_programmable_str(self.skip)
|
|
validate_programmable_str(self.texture)
|
|
for i in self.uvs:
|
|
validate_programmable_str(i)
|
|
if self.normals is not None:
|
|
for i in self.normals:
|
|
validate_programmable_str(i)
|
|
return self
|
|
|
|
|
|
class Instance(BaseModel):
|
|
identifier: str = Field(frozen=True, strict=True)
|
|
skip: str = Field(frozen=True, strict=True)
|
|
params: dict[str, str] = Field(frozen=True, strict=True)
|
|
transform: str = Field(frozen=True, strict=True)
|
|
|
|
@model_validator(mode='after')
|
|
def verify_prog_field(self) -> Self:
|
|
validate_programmable_str(self.skip)
|
|
for v in self.params.values():
|
|
validate_programmable_str(v)
|
|
validate_programmable_str(self.transform)
|
|
return self
|
|
|
|
|
|
IDENTIFIERS: set[str] = set()
|
|
|
|
|
|
class Prototype(BaseModel):
|
|
identifier: str = Field(frozen=True, strict=True)
|
|
showcase: Optional[Showcase] = Field(frozen=True, strict=True)
|
|
params: list[Param] = Field(frozen=True, strict=True)
|
|
skip: str = Field(frozen=True, strict=True)
|
|
vars: list[Var] = Field(frozen=True, strict=True)
|
|
vertices: list[Vertex] = Field(frozen=True, strict=True)
|
|
faces: list[Face] = Field(frozen=True, strict=True)
|
|
instances: list[Instance] = Field(frozen=True, strict=True)
|
|
|
|
@model_validator(mode='after')
|
|
def verify_identifier(self) -> Self:
|
|
global IDENTIFIERS
|
|
if self.identifier in IDENTIFIERS:
|
|
raise ValueError(f'Identifier {self.identifier} is already registered.')
|
|
else:
|
|
IDENTIFIERS.add(self.identifier)
|
|
return self
|
|
|
|
@model_validator(mode='after')
|
|
def verify_prog_field(self) -> Self:
|
|
validate_programmable_str(self.skip)
|
|
return self
|
|
|
|
|
|
class Prototypes(RootModel):
|
|
root: list[Prototype] = Field(frozen=True, strict=True)
|
|
|
|
|
|
def validate_json() -> None:
|
|
raw_json_folder = common.get_plugin_folder() / 'raw_jsons'
|
|
|
|
for json_file in raw_json_folder.rglob('*.json'):
|
|
logging.info(f'Validating {json_file} ...')
|
|
try:
|
|
with open(json_file, 'r', encoding='utf-8') as f:
|
|
docuement = json.load(f)
|
|
Prototypes.model_validate(docuement)
|
|
except json.JSONDecodeError as e:
|
|
logging.error(f'Can not load file {json_file}. It may not a valid JSON file. Reason: {e}')
|
|
except ValidationError as e:
|
|
logging.error(f'File {json_file} is not correct. Reason: {e}')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
common.setup_logging()
|
|
validate_json()
|