Skip to content

Instantly share code, notes, and snippets.

@pl77
Created December 1, 2017 21:20
Show Gist options
  • Save pl77/e03d58d2b25a3c3a5034684c7006c81b to your computer and use it in GitHub Desktop.
Save pl77/e03d58d2b25a3c3a5034684c7006c81b to your computer and use it in GitHub Desktop.
A small script to automate Bodyslide mesh conversions from old Skyrim CBBE to new CBBE format.
from pywinauto.application import Application
from pywinauto.timings import TimeoutError
from pywinauto.findbestmatch import MatchError
from bs4 import BeautifulSoup
import os
import time
import json
def get_soup(filepath):
with open(filepath, 'r', encoding='utf8') as myfile:
rawfile = myfile.read()
try:
soup = BeautifulSoup(rawfile, "lxml")
except (OSError or ValueError):
print("File error.")
soup = None
pass
return soup
def startbodyslide():
# Run a windows shortcut to directly open Outfit Studio
os.startfile(r"E:\Bodyslide\Data\CalienteTools\BodySlide\BodySlideShortcut.lnk")
# Connect the automation framework to the Bodyslide Process
app = Application().connect(path=r"E:\Bodyslide\Data\CalienteTools\BodySlide\BodySlidex64.exe")
# Find the Outfit Studio Window
return app
def processbodyslide(app, dlg, shapedatadir, projectname, pdict):
# Open a new project
dlg.menu_select("File->New Project")
# Connect to the new project window
np = app['New Project']
# Select the Reference
np['From TemplateComboBox'].Select('Convert: SK CBBE To SSE CBBE')
np['&Next >'].click()
# Enter a new project name
np.Edit.SetText(projectname)
# Click on the From File radio button
np['From File3'].click()
# enter the bodyslide nif file path
nifdir = os.path.join(shapedatadir, pdict['setfolder'], pdict['sourcefile'])
np['npWorkFilename'].Edit.SetText(nifdir)
np['&Finish'].click()
# accept the conversion from old skyrim nifs to SSE
try:
app['Target Game']['&Yes'].click()
except (TimeoutError, MatchError):
pass
# window gets renamed with the project name, so find the new name for the Outfit Studio window
windowtitle = 'Outfit Studio - {pn}'.format(pn=projectname)
dlg2 = app[windowtitle]
# conform sliders
dlg2.menu_select("Slider->Conform All")
# find and deleting the old BaseShape from original Skyrim
meshtree = dlg2['TreeView']
meshtreeroots = meshtree.roots()
meshtree_children = meshtreeroots[0].children()
for child in meshtree_children:
child.click()
if child.text() == 'BaseShape':
dlg2.type_keys('{DEL}')
try:
app['Confirm Delete']['&Yes'].click()
except (TimeoutError, MatchError):
pass
# set the reference slider to 100%
tb = dlg2['De-/Select SlidersEdit2']
tb.click()
tb.SetText("100")
tb.click()
time.sleep(1)
# open up the Bones tab for next step
dlg2['BonesButton'].click()
# click somewhere else to make the slider figure out it's been reset
dlg2['mGLView_container'].click()
# find and delete unused bones from the old skeleton
bonestree = app[windowtitle]['TreeView']
bonesroots = bonestree.roots()
for idx, root in enumerate(bonesroots):
try:
root.click()
except AttributeError:
dlg2.print_control_identifiers()
print("Attribute Error")
continue
print(root.text())
if root.text() == "NPC Belly" or root.text() == "NPC L Breast01" or root.text() == "NPC R Breast01":
root.select()
# open up the context menu
root.click_input(button='right')
popup = app['PopupMenu']
# the only way I could find to click through the context menu and delete unused bones from project
popup.MenuItem('Delete').ClickInput()
popup.MenuItem('From Project').ClickInput()
# set base shape and load reference
dlg2.menu_select("Slider->Set Base Shape")
dlg2.menu_select("File->Load Reference")
lr = app['Load Reference']
lr['From TemplateComboBox'].Select('CBBE Body Physics')
lr['&OK'].click()
# conform the outfit to the new reference shape
dlg2.menu_select("Slider->Conform All")
dlg2.menu_select("File->Save As")
svas = app['Save Project As...']
svas['Output File NameEdit'].SetText(pdict['outputfile'])
svas['Output Data PathEdit'].SetText(pdict['outputpath'])
projfolder = '000TEST' # temporary folder for putzing around; need to convert into a variable
sldset = '{pf}\\{proj}.osp'.format(pf=projfolder, proj=projectname) # hardcoded and needs to be made a variable
svas['Slider Set FileEdit'].SetText(sldset)
svas['Shape Data FolderEdit'].SetText(projfolder)
svas['&Save'].click()
# reset the tab to Meshes or the next file will not work.
dlg2['MeshesButton'].click()
return app, dlg2
def parseslidersets(sourcedir, prefix):
setdict = dict()
# walk the dir, pull the data, dump it into a dict and send it back
for dirpath, dirnames, dirfiles in os.walk(sourcedir):
for filename in dirfiles:
if filename.startswith(prefix) and filename.endswith('.xml'):
print(filename)
full_path_name = os.path.join(dirpath, filename)
filesoup = get_soup(full_path_name)
settag = filesoup.html.body.slidersetinfo.sliderset
projectname = settag.attrs['name']
setdict[projectname] = dict()
setdict[projectname]['setfolder'] = settag.setfolder.text
setdict[projectname]['sourcefile'] = settag.sourcefile.text
setdict[projectname]['outputpath'] = settag.outputpath.text
setdict[projectname]['outputfile'] = settag.outputfile.text
baseshapetaglist = list(settag.find_all('baseshapename'))
baseshapelist = list()
for baseshape in baseshapetaglist:
baseshapelist.append(baseshape.attrs)
setdict[projectname]['baseshapelist'] = baseshapelist
slidertaglist = list(settag.find_all('slider'))
sliderlist = list()
for slidertag in slidertaglist:
sliderlist.append(slidertag.attrs)
setdict[projectname]['sliderlist'] = sliderlist
return setdict
def main():
basedir = 'E:\Bodyslide\Data'
slidersetdir = 'CalienteTools\BodySlide\SliderSets'
shapedatadir = 'CalienteTools\BodySlide\ShapeData'
prefix = 'IA - '
sourcedir = os.path.join(basedir, slidersetdir)
slidersetdict = parseslidersets(sourcedir, prefix)
# dump XML dict into a JSON in case I want to use later. Maybe to pause and resume?
with open('slidersets.json', 'w') as jfile:
jfile.write(json.dumps(slidersetdict))
shapedir = os.path.join(basedir, shapedatadir)
app = startbodyslide()
dlg = app['Outfit Studio']
for project, pdict in slidersetdict.items():
app, dlg = processbodyslide(app, dlg, shapedir, project, pdict)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment