6 import xml
.etree
.ElementTree
7 from logging
import getLogger
, StreamHandler
, INFO
, WARNING
, Formatter
, Filter
9 class SingleLevelFilter(Filter
):
10 def __init__(self
, passlevel
, reject
):
11 self
.passlevel
= passlevel
14 def filter(self
, record
):
16 return (record
.levelno
!= self
.passlevel
)
18 return (record
.levelno
== self
.passlevel
)
21 def __init__(self
, mod_name
, vfs_path
):
22 self
.mod_name
= mod_name
23 self
.vfs_path
= vfs_path
24 self
.name
= os
.path
.basename(vfs_path
)
27 self
.logger
= getLogger(__name__
)
29 def read(self
, physical_path
):
31 tree
= xml
.etree
.ElementTree
.parse(physical_path
)
32 except xml
.etree
.ElementTree
.ParseError
as err
:
33 self
.logger
.error('"%s": %s' % (physical_path
, err
.msg
))
36 # Special case: particles don't need a diffuse texture.
37 if len(root
.findall('.//particles')) > 0:
38 self
.textures
.append("baseTex")
40 for element
in root
.findall('.//material'):
41 self
.material
= element
.text
42 for element
in root
.findall('.//texture'):
43 self
.textures
.append(element
.get('name'))
44 for element
in root
.findall('.//variant'):
45 file = element
.get('file')
47 self
.read_variant(physical_path
, os
.path
.join('art', 'variants', file))
50 def read_variant(self
, actor_physical_path
, relative_path
):
51 physical_path
= actor_physical_path
.replace(self
.vfs_path
, relative_path
)
53 tree
= xml
.etree
.ElementTree
.parse(physical_path
)
54 except xml
.etree
.ElementTree
.ParseError
as err
:
55 self
.logger
.error('"%s": %s' % (physical_path
, err
.msg
))
59 file = root
.get('file')
61 self
.read_variant(actor_physical_path
, os
.path
.join('art', 'variants', file))
63 for element
in root
.findall('.//texture'):
64 self
.textures
.append(element
.get('name'))
68 def __init__(self
, mod_name
, vfs_path
):
69 self
.mod_name
= mod_name
70 self
.vfs_path
= vfs_path
71 self
.name
= os
.path
.basename(vfs_path
)
72 self
.required_textures
= []
74 def read(self
, physical_path
):
76 root
= xml
.etree
.ElementTree
.parse(physical_path
).getroot()
77 except xml
.etree
.ElementTree
.ParseError
as err
:
78 self
.logger
.error('"%s": %s' % (physical_path
, err
.msg
))
80 for element
in root
.findall('.//required_texture'):
81 texture_name
= element
.get('name')
82 self
.required_textures
.append(texture_name
)
87 def __init__(self
, vfs_root
, mods
=None):
89 mods
= ['mod', 'public']
91 self
.vfs_root
= vfs_root
94 self
.invalid_materials
= {}
99 def __init_logger(self
):
100 logger
= getLogger(__name__
)
101 logger
.setLevel(INFO
)
102 # create a console handler, seems nicer to Windows and for future uses
103 ch
= StreamHandler(sys
.stdout
)
105 ch
.setFormatter(Formatter('%(levelname)s - %(message)s'))
106 f1
= SingleLevelFilter(INFO
, False)
108 logger
.addHandler(ch
)
109 errorch
= StreamHandler(sys
.stderr
)
110 errorch
.setLevel(WARNING
)
111 errorch
.setFormatter(Formatter('%(levelname)s - %(message)s'))
112 logger
.addHandler(errorch
)
115 def get_mod_path(self
, mod_name
, vfs_path
):
116 return os
.path
.join(mod_name
, vfs_path
)
118 def get_physical_path(self
, mod_name
, vfs_path
):
119 return os
.path
.realpath(os
.path
.join(self
.vfs_root
, mod_name
, vfs_path
))
121 def find_mod_files(self
, mod_name
, vfs_path
, pattern
):
122 physical_path
= self
.get_physical_path(mod_name
, vfs_path
)
124 if not os
.path
.isdir(physical_path
):
126 for file_name
in os
.listdir(physical_path
):
127 if file_name
== '.git' or file_name
== '.svn':
129 vfs_file_path
= os
.path
.join(vfs_path
, file_name
)
130 physical_file_path
= os
.path
.join(physical_path
, file_name
)
131 if os
.path
.isdir(physical_file_path
):
132 result
+= self
.find_mod_files(mod_name
, vfs_file_path
, pattern
)
133 elif os
.path
.isfile(physical_file_path
) and pattern
.match(file_name
):
135 'mod_name': mod_name
,
136 'vfs_path': vfs_file_path
140 def find_all_mods_files(self
, vfs_path
, pattern
):
142 for mod_name
in reversed(self
.mods
):
143 result
+= self
.find_mod_files(mod_name
, vfs_path
, pattern
)
146 def find_materials(self
, vfs_path
):
147 self
.logger
.info('Collecting materials...')
148 material_files
= self
.find_all_mods_files(vfs_path
, re
.compile(r
'.*\.xml'))
149 for material_file
in material_files
:
150 material_name
= os
.path
.basename(material_file
['vfs_path'])
151 if material_name
in self
.materials
:
153 material
= Material(material_file
['mod_name'], material_file
['vfs_path'])
154 if material
.read(self
.get_physical_path(material_file
['mod_name'], material_file
['vfs_path'])):
155 self
.materials
[material_name
] = material
157 self
.invalid_materials
[material_name
] = material
159 def find_actors(self
, vfs_path
):
160 self
.logger
.info('Collecting actors...')
162 actor_files
= self
.find_all_mods_files(vfs_path
, re
.compile(r
'.*\.xml'))
163 for actor_file
in actor_files
:
164 actor
= Actor(actor_file
['mod_name'], actor_file
['vfs_path'])
165 if actor
.read(self
.get_physical_path(actor_file
['mod_name'], actor_file
['vfs_path'])):
166 self
.actors
.append(actor
)
169 self
.find_materials(os
.path
.join('art', 'materials'))
170 self
.find_actors(os
.path
.join('art', 'actors'))
171 self
.logger
.info('Validating textures...')
173 for actor
in self
.actors
:
174 if not actor
.material
:
176 if actor
.material
not in self
.materials
and actor
.material
not in self
.invalid_materials
:
177 self
.logger
.error('"%s": unknown material "%s"' % (
178 self
.get_mod_path(actor
.mod_name
, actor
.vfs_path
),
181 if actor
.material
not in self
.materials
:
183 material
= self
.materials
[actor
.material
]
185 missing_textures
= ', '.join(set([required_texture
for required_texture
in material
.required_textures
if required_texture
not in actor
.textures
]))
186 if len(missing_textures
) > 0:
187 self
.logger
.error('"%s": actor does not contain required texture(s) "%s" from "%s"' % (
188 self
.get_mod_path(actor
.mod_name
, actor
.vfs_path
),
193 extra_textures
= ', '.join(set([extra_texture
for extra_texture
in actor
.textures
if extra_texture
not in material
.required_textures
]))
194 if len(extra_textures
) > 0:
195 self
.logger
.warning('"%s": actor contains unnecessary texture(s) "%s" from "%s"' % (
196 self
.get_mod_path(actor
.mod_name
, actor
.vfs_path
),
201 if __name__
== '__main__':
202 script_dir
= os
.path
.dirname(os
.path
.realpath(__file__
))
203 default_root
= os
.path
.join(script_dir
, '..', '..', '..', 'binaries', 'data', 'mods')
204 parser
= argparse
.ArgumentParser(description
='Actors/materials validator.')
205 parser
.add_argument('-r', '--root', action
='store', dest
='root', default
=default_root
)
206 parser
.add_argument('-m', '--mods', action
='store', dest
='mods', default
='mod,public')
207 args
= parser
.parse_args()
208 validator
= Validator(args
.root
, args
.mods
.split(','))