-
Notifications
You must be signed in to change notification settings - Fork 0
/
auto_qc.py
163 lines (132 loc) · 5.6 KB
/
auto_qc.py
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import os, time, math, tempfile
from dataclasses import dataclass
from mathutils import Matrix, Vector
from . import sanitize_dmx
@dataclass
class CompileInputs:
model_path: str
mesh_paths: list[str]
cdmaterials: str
_temp_meshes: list = list
_pre_existing_qc: str or None = None
@classmethod
def from_qc_file(cls, path: str):
model_path = None
mesh_paths = []
cdmaterials = None
keyvalues = {}
print("Parsing QC file:", path)
with open(path) as f:
for line in f:
tokens = line.split()
for i, token in enumerate(tokens):
if token.startswith("//"):
tokens = tokens[:i]
break
if not tokens:
continue
if tokens[0].startswith("$"):
key = tokens[0][1:]
if len(tokens) == 1:
value = True
if len(tokens) == 2:
value = tokens[1].strip("\"")
else:
value = tuple(t.strip("\"") for t in tokens[1:])
keyvalues[key] = value
if key.lower() == "model":
mesh_paths.append(tokens[2].strip("\""))
elif key.lower() == "modelname":
assert type(value) is str
model_path = value
elif key.lower() == "cdmaterials":
assert type(value) is str
cdmaterials = value
else:
print("WARNING - ignoring line:", line.strip())
print(model_path)
print(mesh_paths)
print(cdmaterials)
print(keyvalues)
if not all([model_path, mesh_paths, cdmaterials]):
raise ValueError("Couldn't parse all necessary data from the QC!")
return cls(model_path, mesh_paths, cdmaterials, _pre_existing_qc=path)
@classmethod
def from_mesh_file(cls, path: str):
mesh_name, extension = os.path.splitext(os.path.basename(path))
# TODO: more sensible defaults
user = os.environ["USERNAME"].lower()
model_path = "{}/{}.mdl".format(user, mesh_name)
cdmaterials = "models/{}/autoqc".format(user)
# If it's a DMX, create a sanitized version first
# TODO: check whether this is actually necessary depending on studiomdl's requirements
if extension.lower() == ".dmx":
temp_meshes = [TemporarySanitisedDMX(path)]
mesh_paths = []
else:
temp_meshes = []
mesh_paths = [path]
return cls(model_path, mesh_paths, cdmaterials, _temp_meshes=temp_meshes)
@property
def model_name(self):
return os.path.splitext(os.path.basename(self.model_path))[0]
# Returns a context manager, not the path itself
def get_qc_with_dependencies(self):
if self._pre_existing_qc:
return TemporaryButActuallyNotTemporaryFile(self._pre_existing_qc)
else:
qc_file = TemporaryQCFile(self)
return qc_file
class TemporaryButActuallyNotTemporaryFile:
def __init__(self, thing):
self._thing = thing
def __enter__(self):
return self._thing
def __exit__(self, exc_type, exc_value, traceback):
pass
class TemporarySanitisedDMX:
def __init__(self, input_path: str, clear_material_path: bool = True):
self._input_path = input_path
self._clear_material_path = clear_material_path
mesh_name, extension = os.path.splitext(os.path.basename(self._input_path))
mesh_name = mesh_name + "_" + next(tempfile._get_candidate_names())
self._output_path = os.path.join(os.path.dirname(self._input_path), mesh_name + ".dmx")
def __enter__(self) -> str:
print("Sanitising DMX", self._input_path, "using temp path", self._output_path)
sanitize_dmx.external_sanitize_dmx(
self._input_path, self._output_path, clear_material_path=self._clear_material_path
)
return self._output_path
def __exit__(self, exc_type, exc_value, traceback):
os.remove(self._output_path)
class TemporaryQCFile:
def __init__(self, compile_inputs: CompileInputs):
self._compile_inputs = compile_inputs
self._path = None
def __enter__(self) -> str:
# TODO: support multiple meshes
if self._compile_inputs._temp_meshes:
mesh_path = self._compile_inputs._temp_meshes[0].__enter__()
self._compile_inputs.mesh_paths.append(mesh_path)
else:
mesh_path = self._compile_inputs.mesh_paths[0]
mesh_name, _ = os.path.splitext(os.path.basename(mesh_path))
qc_template = '$modelname "{model_path}"\n$cdmaterials {cdmaterials}\n$staticprop\n$model "studio" "{mesh_name}"\n$sequence idle "{mesh_name}" loop fps 1.00\n'
qc_file = tempfile.NamedTemporaryFile(
mode="w", dir=os.path.dirname(mesh_path), suffix=".qc", delete=False
)
qc_file.write(
qc_template.format(
model_path=self._compile_inputs.model_path,
mesh_name=mesh_name,
cdmaterials=self._compile_inputs.cdmaterials,
)
)
qc_file.close()
print("Creating temporary QC file", qc_file.name)
self._path = qc_file.name
return self._path
def __exit__(self, exc_type, exc_value, traceback):
os.remove(self._path)
for temp_mesh in self._compile_inputs._temp_meshes:
temp_mesh.__exit__(None, None, None)