File: validate_dae.py

package info (click to toggle)
0ad 0.28.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 182,352 kB
  • sloc: cpp: 201,989; javascript: 19,730; ansic: 15,057; python: 6,597; sh: 2,046; perl: 1,232; xml: 543; java: 533; makefile: 105
file content (121 lines) | stat: -rw-r--r-- 3,921 bytes parent folder | download | duplicates (2)
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
115
116
117
118
119
120
121
#!/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()