Last active
November 25, 2018 05:23
-
-
Save Rainyan/43b83ca975d9382a00f520ec091f8d3a to your computer and use it in GitHub Desktop.
Python script to generate QC files for Source SDK studiomdl compiler.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
"""Generate a QC file for Source SDK studiomdl compile.""" | |
# TODO: 2 separate commands for | |
# 1. generating qc (and storing to file) without running studiomdl | |
# 2. running studiomdl with the qc | |
import os | |
import subprocess | |
import warnings | |
# Link to the latest source code for this script. | |
SOURCECODE_URL = 'https://gist.github.com/Rainyan/43b83ca975d9382a00f520ec091f8d3a' | |
SOURCECODE_VERSION = '2018-11-25' | |
# This is the game root path with GameInfo.txt, materials, models, etc. | |
GAME_PATH = 'C:/Program Files (x86)/Steam/steamapps/common/NEOTOKYO/mapping' | |
QC_PATH = os.path.dirname(os.path.realpath(__file__)) + '/buildmodel.qc' | |
STUDIOMDL_BIN = 'C:/Program Files (x86)/Steam/steamapps/common/SourceSDK/bin/ep1/bin/studiomdl.exe' | |
# Folder path inside "models"; leave empty to use the root /models/ as location | |
MODELNAME_PATH = 'custom/snowfall' | |
# Name of the base model | |
MODELNAME_BASE = 'tunnel_maincurve' + '.smd' | |
# Name of the collision model | |
MODELNAME_COLLISION = 'hacd_tunnel_maincurve' + '.collision.smd' | |
# SMD path, relative to this script location | |
STUDIO_MDL_PATH = 'models' | |
# $cdmaterials path | |
CD_MATERIALS_PATH = 'models/' + MODELNAME_PATH | |
# Is Source engine version EP1 being used? | |
IS_EP1_ENGINE = True | |
# $staticprop, not necessarily prop_static | |
IS_STATICPROP = True | |
# Is collision concave? | |
IS_CONCAVE = False | |
# Is collision costly? | |
IS_COSTLY_COLLISION_MODEL = False | |
# Is physics prop? | |
IS_PHYSICAL = False | |
# Show generated QC in terminal (this can make Python warnings hard to find)? | |
OUTPUT_GENERATED_QC = False | |
if IS_COSTLY_COLLISION_MODEL and not IS_EP1_ENGINE: | |
# For Orange Box and newer, $collisionmodel->$maxconvexpieces sets the max | |
# collision model pieces for costly collision. This defaults to 20, and should | |
# be set higher than that (this is sanity checked for in the QC generation). | |
OB_MAX_CONVEX_PIECES = 20 | |
# https://developer.valvesoftware.com/wiki/Prop_data_base_types | |
PROP_DATA_BASE_TYPE = 'Stone.Large' | |
# https://developer.valvesoftware.com/wiki/Material_surface_properties | |
SURFACE_PROP = 'concrete' | |
# float, in kg | |
MASS = 1000.0 | |
# Helpers for output nicifying | |
TAB_OFFSET = ' ' | |
OPEN_BRACE = '{' | |
CLOSE_BRACE = '}' | |
def main(): | |
"""Script entrypoint.""" | |
qc = generate_qc() | |
write_qc_to_file(qc) | |
run_studiomdl() | |
def write_qc_to_file(qc): | |
"""Iterate through a string list of QC data, | |
and write it to file.""" | |
print('- Writing QC to "' + QC_PATH + '"...') | |
if (OUTPUT_GENERATED_QC): print('') | |
f = open(QC_PATH, 'w') | |
for line in qc: | |
if (OUTPUT_GENERATED_QC): print(line) | |
f.write(line + '\n') | |
f.close() | |
if (OUTPUT_GENERATED_QC): print('') | |
print('- QC file ready.') | |
def generate_qc(): | |
"""Build a string list of relevant QC data to process.""" | |
print('- Building QC data...') | |
qc = [] | |
qc.append('// This QC file was auto-generated by a Python script:') | |
qc.append('// ' + os.path.basename(__file__) + ' version ' + SOURCECODE_VERSION) | |
qc.append('// Source: ' + SOURCECODE_URL) | |
qc.append('// Note that any manual edits will get overwritten if the script is run again!') | |
qc.append('') | |
# modelname | |
qc.append('$modelname "' + MODELNAME_PATH + '/' + MODELNAME_BASE + '"') | |
qc.append('$cdmaterials "' + CD_MATERIALS_PATH + '"') | |
qc.append('') | |
# body | |
qc.append('$body studio "' + STUDIO_MDL_PATH + '/' + MODELNAME_BASE + '"') | |
if (IS_STATICPROP): | |
qc.append('$staticprop') | |
qc.append('$surfaceprop ' + SURFACE_PROP) | |
qc.append('') | |
# sequence | |
qc.append('$sequence idle "' + STUDIO_MDL_PATH + '/' + MODELNAME_BASE + '"') | |
qc.append('') | |
# collisionmodel | |
if IS_COSTLY_COLLISION_MODEL and IS_EP1_ENGINE: | |
qc.append('// On ep1 engine, run studiomdl with -fullcollide ' +\ | |
'if compiling costly collision models.') | |
qc.append('// For details, see: ' +\ | |
'https://developer.valvesoftware.com/wiki/Costly_collision_model') | |
qc.append('$collisionmodel "' + STUDIO_MDL_PATH + '/' + MODELNAME_COLLISION + '"') | |
qc.append(OPEN_BRACE) | |
qc.append(TAB_OFFSET + '$mass ' + str(MASS)) | |
if (IS_CONCAVE): | |
qc.append(TAB_OFFSET + '$concave') | |
if IS_COSTLY_COLLISION_MODEL and not IS_EP1_ENGINE: | |
if OB_MAX_CONVEX_PIECES <= 20: | |
warning = 'Compiling OB costly collision with $maxconvexpieces <= default! ' +\ | |
'This is probably a mistake.' | |
warnings.warn(warning) | |
qc.append(TAB_OFFSET + '// WARNING: ' + warning) | |
qc.append(TAB_OFFSET + '$maxconvexpieces ' + str(OB_MAX_CONVEX_PIECES)) | |
qc.append(CLOSE_BRACE) | |
# keyvalues | |
if (IS_PHYSICAL): | |
qc.append('') | |
qc.append('$keyvalues') | |
qc.append(OPEN_BRACE) | |
qc.append(TAB_OFFSET + 'prop_data') | |
qc.append(TAB_OFFSET + OPEN_BRACE) | |
qc.append(TAB_OFFSET + TAB_OFFSET + 'base ' + PROP_DATA_BASE_TYPE) | |
qc.append(TAB_OFFSET + CLOSE_BRACE) | |
qc.append(CLOSE_BRACE) | |
return qc | |
def list_to_str(list): | |
"""Take a list and output it as a concatenated | |
string with whitespace dividers.""" | |
output = '' | |
for item in list: | |
output += ' ' + item | |
output += ' ' | |
return output | |
def run_studiomdl(): | |
"""Call studiomdl to process the generated QC file, | |
and compile it into Source engine model data.""" | |
print('- Running studiomdl...') | |
args = ['-game', GAME_PATH, '-nop4', '-verbose'] | |
if (IS_COSTLY_COLLISION_MODEL): | |
args.append('-fullcollide') | |
command = '"' + STUDIOMDL_BIN + '"' + list_to_str(args) + '"' + QC_PATH + '"' | |
print(command) | |
subprocess.run(command) | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment