284 lines
11 KiB
Python
284 lines
11 KiB
Python
import base64
|
|
import json
|
|
import re
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import zipfile
|
|
|
|
try:
|
|
import zlib
|
|
|
|
compression = zipfile.ZIP_DEFLATED
|
|
except:
|
|
compression = zipfile.ZIP_STORED
|
|
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
def convertDirectoryToZipFile(directoryPath):
|
|
if os.path.isfile(directoryPath):
|
|
return
|
|
|
|
zipFilePath = "%s.zip" % directoryPath
|
|
zf = zipfile.ZipFile(zipFilePath, mode="w")
|
|
|
|
try:
|
|
for dirName, subdirList, fileList in os.walk(directoryPath):
|
|
for fname in fileList:
|
|
fullPath = os.path.join(dirName, fname)
|
|
relPath = "%s" % (os.path.relpath(fullPath, directoryPath))
|
|
zf.write(fullPath, arcname=relPath, compress_type=compression)
|
|
finally:
|
|
zf.close()
|
|
|
|
shutil.rmtree(directoryPath)
|
|
shutil.move(zipFilePath, directoryPath)
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
def addDataToViewer(dataPath, srcHtmlPath, disableGirder=False):
|
|
if os.path.isfile(dataPath) and os.path.exists(srcHtmlPath):
|
|
dstDir = os.path.dirname(dataPath)
|
|
dstHtmlPath = os.path.join(dstDir, "%s.html" % os.path.basename(dataPath)[:-6])
|
|
|
|
# Extract data as base64
|
|
with open(dataPath, "rb") as data:
|
|
dataContent = data.read()
|
|
base64Content = base64.b64encode(dataContent)
|
|
base64Content = base64Content.decode().replace("\n", "")
|
|
|
|
# Create new output file
|
|
with open(srcHtmlPath, mode="r", encoding="utf-8") as srcHtml:
|
|
with open(dstHtmlPath, mode="w", encoding="utf-8") as dstHtml:
|
|
for line in srcHtml:
|
|
if disableGirder and "</title>" in line:
|
|
dstHtml.write(
|
|
"""
|
|
<script>
|
|
// Force reloading the page if we want to disable girder before anything else.
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
if (urlParams.get('noGirder') != 'true') {
|
|
urlParams.set('noGirder', 'true');
|
|
window.location.search = urlParams;
|
|
}
|
|
</script>
|
|
"""
|
|
)
|
|
if "</body>" in line:
|
|
dstHtml.write("<script>\n")
|
|
dstHtml.write('var contentToLoad = "%s";\n\n' % base64Content)
|
|
dstHtml.write(
|
|
'Glance.importBase64Dataset("%s" , contentToLoad, glanceInstance.proxyManager);\n'
|
|
% os.path.basename(dataPath)
|
|
)
|
|
dstHtml.write("glanceInstance.showApp();\n")
|
|
dstHtml.write("</script>\n")
|
|
|
|
dstHtml.write(line)
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
def numericSorted(l):
|
|
"""Numerically sort a list of strings."""
|
|
|
|
# pattern to split name into numeric and non-numeric parts
|
|
splitter_pattern = re.compile('([0-9]+|[^0-9]+)')
|
|
|
|
def keyfunc(name):
|
|
"""Sorting key for numeric sorting."""
|
|
split_name = re.findall(splitter_pattern, name)
|
|
# one-liner to convert numeric parts into integers
|
|
split_name = list(map(lambda x: int(x) if x.isdigit() else x, split_name))
|
|
# ensure that list begins with a string to avoid string<->int compare
|
|
if split_name and isinstance(split_name[0], int):
|
|
split_name.insert(0, '')
|
|
return split_name
|
|
|
|
# return the numerically sorted list
|
|
return sorted(l, key=keyfunc)
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
def zipAllTimeSteps(directoryPath):
|
|
if os.path.isfile(directoryPath):
|
|
return
|
|
|
|
class UrlCounterDict(dict):
|
|
Counter = 0
|
|
|
|
def GetUrlName(self, name):
|
|
if name not in self.keys():
|
|
self[name] = str(objNameToUrls.Counter)
|
|
self.Counter = self.Counter + 1
|
|
return self[name]
|
|
|
|
def InitIndex(sourcePath, destObj):
|
|
with open(sourcePath, "r") as sourceFile:
|
|
sourceData = sourceFile.read()
|
|
sourceObj = json.loads(sourceData)
|
|
for key in sourceObj:
|
|
destObj[key] = sourceObj[key]
|
|
# remove vtkHttpDataSetReader information
|
|
for obj in destObj["scene"]:
|
|
obj.pop(obj["type"])
|
|
obj.pop("type")
|
|
|
|
def getUrlToNameDictionary(indexObj):
|
|
urls = {}
|
|
for obj in indexObj["scene"]:
|
|
urls[obj[obj["type"]]["url"]] = obj["name"]
|
|
return urls
|
|
|
|
def addDirectoryToZip(
|
|
dirname, zipobj, storedData, rootIdx, timeStep, objNameToUrls
|
|
):
|
|
# Update root index.json file from index.json of this timestep
|
|
with open(os.path.join(dirname, "index.json"), "r") as currentIdxFile:
|
|
currentIdx = json.loads(currentIdxFile.read())
|
|
urlToName = getUrlToNameDictionary(currentIdx)
|
|
rootTimeStepSection = rootIdx["animation"]["timeSteps"][timeStep]
|
|
for key in currentIdx:
|
|
if key == "scene" or key == "version":
|
|
continue
|
|
rootTimeStepSection[key] = currentIdx[key]
|
|
for obj in currentIdx["scene"]:
|
|
objName = obj["name"]
|
|
rootTimeStepSection[objName] = {}
|
|
rootTimeStepSection[objName]["actor"] = obj["actor"]
|
|
rootTimeStepSection[objName]["actorRotation"] = obj["actorRotation"]
|
|
rootTimeStepSection[objName]["mapper"] = obj["mapper"]
|
|
rootTimeStepSection[objName]["property"] = obj["property"]
|
|
|
|
# For every object in the current timestep
|
|
for folder in sorted(os.listdir(dirname)):
|
|
currentItem = os.path.join(dirname, folder)
|
|
if os.path.isdir(currentItem) is False:
|
|
continue
|
|
# Write all data array of the current timestep in the archive
|
|
for filename in os.listdir(os.path.join(currentItem, "data")):
|
|
fullpath = os.path.join(currentItem, "data", filename)
|
|
if os.path.isfile(fullpath) and filename not in storedData:
|
|
storedData.add(filename)
|
|
relPath = os.path.join("data", filename)
|
|
zipobj.write(fullpath, arcname=relPath, compress_type=compression)
|
|
# Write the index.json containing pointers to these data arrays
|
|
# while replacing every basepath as '../../data'
|
|
objIndexFilePath = os.path.join(dirname, folder, "index.json")
|
|
with open(objIndexFilePath, "r") as objIndexFile:
|
|
objIndexObjData = json.loads(objIndexFile.read())
|
|
for elm in objIndexObjData.keys():
|
|
try:
|
|
if "ref" in objIndexObjData[elm].keys():
|
|
objIndexObjData[elm]["ref"]["basepath"] = "../../data"
|
|
if "arrays" in objIndexObjData[elm].keys():
|
|
for array in objIndexObjData[elm]["arrays"]:
|
|
array["data"]["ref"]["basepath"] = "../../data"
|
|
except AttributeError:
|
|
continue
|
|
currentObjName = urlToName[folder]
|
|
objIndexRelPath = os.path.join(
|
|
objNameToUrls.GetUrlName(currentObjName), str(timeStep), "index.json"
|
|
)
|
|
zipobj.writestr(
|
|
objIndexRelPath,
|
|
json.dumps(objIndexObjData, indent=2),
|
|
compress_type=compression,
|
|
)
|
|
|
|
# ---
|
|
|
|
zipFilePath = "%s.zip" % directoryPath
|
|
currentDirectory = os.path.abspath(os.path.join(directoryPath, os.pardir))
|
|
rootIndexPath = os.path.join(currentDirectory, "index.json")
|
|
rootIndexFile = open(rootIndexPath, "r")
|
|
rootIndexObj = json.loads(rootIndexFile.read())
|
|
|
|
zf = zipfile.ZipFile(zipFilePath, mode="w")
|
|
try:
|
|
# We copy the scene from an index of a specific timestep to the root index
|
|
# Scenes should all have the same objects so only do it for the first one
|
|
isSceneInitialized = False
|
|
# currentlyAddedData set stores hashes of every data we already added to the
|
|
# vtkjs archive to prevent data duplication
|
|
currentlyAddedData = set()
|
|
# Regex that folders storing timestep data from paraview should follow
|
|
reg = re.compile(r"^" + os.path.basename(directoryPath) + r"\.[0-9]+$")
|
|
# We assume an object will not be deleted from a timestep to another so we create a generic index.json for each object
|
|
genericIndexObj = {}
|
|
genericIndexObj["series"] = []
|
|
timeStep = 0
|
|
for item in rootIndexObj["animation"]["timeSteps"]:
|
|
genericIndexObj["series"].append({})
|
|
genericIndexObj["series"][timeStep]["url"] = str(timeStep)
|
|
genericIndexObj["series"][timeStep]["timeStep"] = float(item["time"])
|
|
timeStep = timeStep + 1
|
|
# Keep track of the url for every object
|
|
objNameToUrls = UrlCounterDict()
|
|
|
|
timeStep = 0
|
|
# zip all timestep directories
|
|
for folder in numericSorted(os.listdir(currentDirectory)):
|
|
fullPath = os.path.join(currentDirectory, folder)
|
|
if os.path.isdir(fullPath) and reg.match(folder):
|
|
if not isSceneInitialized:
|
|
InitIndex(os.path.join(fullPath, "index.json"), rootIndexObj)
|
|
isSceneInitialized = True
|
|
addDirectoryToZip(
|
|
fullPath,
|
|
zf,
|
|
currentlyAddedData,
|
|
rootIndexObj,
|
|
timeStep,
|
|
objNameToUrls,
|
|
)
|
|
shutil.rmtree(fullPath)
|
|
timeStep = timeStep + 1
|
|
|
|
# Write every index.json holding time information for each object
|
|
for name in objNameToUrls:
|
|
zf.writestr(
|
|
os.path.join(objNameToUrls[name], "index.json"),
|
|
json.dumps(genericIndexObj, indent=2),
|
|
compress_type=compression,
|
|
)
|
|
|
|
# Update root index.json urls and write it in the archive
|
|
for obj in rootIndexObj["scene"]:
|
|
obj["id"] = obj["name"]
|
|
obj["type"] = "vtkHttpDataSetSeriesReader"
|
|
obj["vtkHttpDataSetSeriesReader"] = {}
|
|
obj["vtkHttpDataSetSeriesReader"]["url"] = objNameToUrls[obj["name"]]
|
|
zf.writestr(
|
|
"index.json", json.dumps(rootIndexObj, indent=2), compress_type=compression
|
|
)
|
|
os.remove(rootIndexPath)
|
|
|
|
finally:
|
|
zf.close()
|
|
|
|
shutil.move(zipFilePath, directoryPath)
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Main
|
|
# -----------------------------------------------------------------------------
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2:
|
|
print(
|
|
"Usage: directoryToFile /path/to/directory.vtkjs [/path/to/ParaViewGlance.html]"
|
|
)
|
|
else:
|
|
fileName = sys.argv[1]
|
|
convertDirectoryToZipFile(fileName)
|
|
|
|
if len(sys.argv) == 3:
|
|
addDataToViewer(fileName, sys.argv[2])
|