forked from mirrors/0ad
Refactor validate_dae.py for more speed
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.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
#!/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
|
||||
|
||||
@@ -8,36 +10,32 @@ from collada import Collada, DaeBrokenRefError, DaeError, common, controller
|
||||
from scriptlib import find_files
|
||||
|
||||
|
||||
def validate_vertex(path_dae, log=print):
|
||||
has_weightless = False
|
||||
had_exception = False
|
||||
dae = None
|
||||
try:
|
||||
dae = Collada(path_dae, ignore=[common.DaeUnsupportedError, DaeBrokenRefError])
|
||||
except DaeError as inst:
|
||||
log("Failed to load %s", path_dae)
|
||||
log(type(inst), inst)
|
||||
had_exception = True
|
||||
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:
|
||||
had_exception = True
|
||||
break
|
||||
raise InvalidControllerError
|
||||
totalv = len(ctr.vcounts)
|
||||
totalv_0 = len(ctr.vcounts[ctr.vcounts == 0])
|
||||
if totalv_0 > 0:
|
||||
log(
|
||||
"Mesh %s has %i (out of %i) vertices with no weight"
|
||||
" and no bone assigned. Use P294 to find them in Blender.",
|
||||
path_dae,
|
||||
totalv_0,
|
||||
totalv,
|
||||
)
|
||||
has_weightless = True
|
||||
return (int(has_weightless) << 1) | int(had_exception)
|
||||
raise VertexWithoutWeightError(totalv, totalv_0)
|
||||
|
||||
|
||||
class DaeValidator:
|
||||
def __init__(self, vfs_root, mods):
|
||||
def __init__(self, vfs_root: Path, mods: list[str]) -> None:
|
||||
self.has_weightless_vtx = []
|
||||
self.vfs_root = vfs_root
|
||||
self.mods = mods
|
||||
@@ -49,17 +47,43 @@ class DaeValidator:
|
||||
sh.setFormatter(Formatter("%(levelname)s - %(message)s"))
|
||||
self.log.addHandler(sh)
|
||||
|
||||
def run(self):
|
||||
def run(self) -> bool:
|
||||
"""Run validation for a bunch of meshes."""
|
||||
is_ok = True
|
||||
files = find_files(self.vfs_root, self.mods, Path("art/meshes"), ["dae"])
|
||||
i = 0
|
||||
for _, dae in files:
|
||||
i += 1
|
||||
status = validate_vertex(dae.as_posix(), self.log.warning)
|
||||
if status >= 1:
|
||||
is_ok = False
|
||||
if status >= 2:
|
||||
self.has_weightless_vtx.append(dae)
|
||||
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),
|
||||
@@ -68,9 +92,30 @@ class DaeValidator:
|
||||
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__":
|
||||
vfsr = Path(__file__).resolve().parents[3] / "binaries" / "data" / "mods"
|
||||
mods = ["public"]
|
||||
dv = DaeValidator(vfsr, mods)
|
||||
print(f"DaeValidator returns {dv.run()}")
|
||||
print(dv.has_weightless_vtx)
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user