mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 13:23:56 -07:00
This refactors validate_dae.py to utilize multiple CPU cores. This makes it significantly faster. It also improves code quality and adds commands line options.
121 lines
3.8 KiB
Python
Executable file
121 lines
3.8 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
import sys
|
|
from argparse import ArgumentParser
|
|
from concurrent.futures import ProcessPoolExecutor, as_completed
|
|
from logging import INFO, Formatter, StreamHandler, getLogger
|
|
from pathlib import Path
|
|
|
|
from collada import Collada, DaeBrokenRefError, DaeError, common, controller
|
|
from scriptlib import find_files
|
|
|
|
|
|
class InvalidControllerError(Exception):
|
|
"""Mesh contains controllers of other types than Skin."""
|
|
|
|
|
|
class VertexWithoutWeightError(Exception):
|
|
"""Vertex has no weight."""
|
|
|
|
def __init__(self, num_vertices: int, num_vertices_no_weight: int) -> None:
|
|
self.num_vertices = num_vertices
|
|
self.num_vertices_no_weight = num_vertices_no_weight
|
|
|
|
|
|
def validate_mesh(path_dae: Path) -> None:
|
|
"""Validate a mesh."""
|
|
dae = Collada(path_dae.as_posix(), ignore=[common.DaeUnsupportedError, DaeBrokenRefError])
|
|
for ctr in dae.controllers:
|
|
if type(ctr) is not controller.Skin:
|
|
raise InvalidControllerError
|
|
totalv = len(ctr.vcounts)
|
|
totalv_0 = len(ctr.vcounts[ctr.vcounts == 0])
|
|
if totalv_0 > 0:
|
|
raise VertexWithoutWeightError(totalv, totalv_0)
|
|
|
|
|
|
class DaeValidator:
|
|
def __init__(self, vfs_root: Path, mods: list[str]) -> None:
|
|
self.has_weightless_vtx = []
|
|
self.vfs_root = vfs_root
|
|
self.mods = mods
|
|
|
|
self.log = getLogger()
|
|
self.log.setLevel(INFO)
|
|
sh = StreamHandler(sys.stdout)
|
|
sh.setLevel(INFO)
|
|
sh.setFormatter(Formatter("%(levelname)s - %(message)s"))
|
|
self.log.addHandler(sh)
|
|
|
|
def run(self) -> bool:
|
|
"""Run validation for a bunch of meshes."""
|
|
is_ok = True
|
|
i = 0
|
|
with ProcessPoolExecutor() as executor:
|
|
futures = {}
|
|
for _, dae_path in find_files(self.vfs_root, self.mods, Path("art/meshes"), ["dae"]):
|
|
future = executor.submit(validate_mesh, dae_path)
|
|
futures[future] = dae_path
|
|
|
|
for future in as_completed(futures):
|
|
i += 1
|
|
|
|
dae_path = futures[future]
|
|
|
|
try:
|
|
future.result()
|
|
except InvalidControllerError:
|
|
self.log.warning("Mesh %s contains an invalid controller.", dae_path)
|
|
is_ok = False
|
|
continue
|
|
except DaeError as exc:
|
|
self.log.warning("Failed to load mesh %s: %s", dae_path, exc)
|
|
is_ok = False
|
|
continue
|
|
except VertexWithoutWeightError as exc:
|
|
self.log.warning(
|
|
"Mesh %s has %i (out of %i) vertices with no weight"
|
|
" and no bone assigned. Use P294 to find them in Blender.",
|
|
dae_path,
|
|
exc.num_vertices_no_weight,
|
|
exc.num_vertices,
|
|
)
|
|
self.has_weightless_vtx.append(dae_path)
|
|
is_ok = False
|
|
continue
|
|
|
|
self.log.info(
|
|
"%i out of %i files have vertices with no weight or bones.",
|
|
len(self.has_weightless_vtx),
|
|
i,
|
|
)
|
|
return is_ok
|
|
|
|
|
|
def main() -> None:
|
|
parser = ArgumentParser(description="Validate COLLADA meshes.")
|
|
parser.add_argument(
|
|
"-m",
|
|
"--mod-name",
|
|
dest="mod_names",
|
|
nargs="*",
|
|
default=["public"],
|
|
help="The name of the mod to validate.",
|
|
)
|
|
parser.add_argument(
|
|
"-r",
|
|
"--root",
|
|
dest="vfs_root",
|
|
default=Path(__file__).resolve().parents[3] / "binaries" / "data" / "mods",
|
|
type=Path,
|
|
help="The path to mod's root location.",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
dv = DaeValidator(args.vfs_root, args.mod_names)
|
|
if dv.run() > 0:
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|