On this page |
fileResponse(file_path, content_type=None, delete_file=False, download_as_filename=None):
→ hwebserver.Response
-
Any time you are serving the contents of a file on disk, you should use this function instead of reading and returning the contents as a string.
-
For static files (such as for CSS, JavaScript, image, and video files),use hwebserver.registerStaticFilesDirectory to set up the server to automatically serve them instead of serving them in a Python handler. Serving static files using this approach is more performant and does not invoke Python.
-
The file’s contents are not all loaded into memory at once.
file_path
The file path of the file on disk to download to the client.
content_type
The MIME content type of the response. If this is None
(the default), the function uses mimetypes.guess_type()
to guess the MIME type from the disk file’s extension.
delete_file
If this is True
, the server deletes the file after sending it to the client. This is useful for temp files, but could be very dangerous if you change how the code works but forget to turn this off.
download_as_filename
You can optionally supply the filename string the client will download the file as. The server will send a Content-Disposition
header.
import hou import hwebserver @hwebserver.urlHandler("/textures", prefix=True) def texture_download(request): path = request.path() assert path.startswith("/textures") parm_name = path[10:] if not parm_name: return hwebserver.notFoundResponse(request) node = hou.node("/mat/my_material") texture_parm = node.parm(parm_name) texture_path = texture_parm.evalAsString() return hwebserver.fileResponse(request, texture_path)
Serving relative files ¶
Sometimes you want to serve files using a path relative to the Python script file containing the handler. Usually the __file__
variable contains the path of the currently running file, so you can get the directory containing it using os.path.dirname(__file__)
.
However, sometimes __file__
is not defined, such as when you launch Houdini from the command line with a Python script. To compensate for this, you can use the following function:
webutils.py
import os import inspect def script_file_dir(): try: file_path = __file__ except NameError: file_path = inspect.getfile(inspect.currentframe()) if not os.path.isabs(file_path): file_path = os.path.normpath(os.path.join(os.getcwd(), file_path)) return os.path.dirname(file_path)
Rendered image example ¶
import os import tempfile import hou import hwebserver @hwebserver.urlHandler("/tommy.jpg") def tommy(request): """Render an image of the Tommy test geometry using Mantra.""" rop = hou.node("/out/tommy_rop") if rop is None: rop = create_tommy_scene() rop.render() return hwebserver.fileResponse(rop.evalParm("vm_picture")) def create_tommy_scene(): sop = hou.node("/obj").createNode("geo").createNode("testgeometry_tommy") cam = hou.node("/obj").createNode("cam", "tommy_cam") cam.parmTuple("t").set((0, 1.3, 2.3)) output_file = os.path.join(tempfile.gettempdir(), "tommy.jpg") rop = hou.node("/out").createNode("ifd", "tommy_rop") rop.parm("camera").set(cam.path()) rop.parm("vobject").set(sop.parent().name()) rop.parm("vm_picture").set(output_file) return rop
3D web viewer example ¶
This example uses the GLTF ROP to save the Crag test geometry to GTLF format and displays it in the browser using Babylon.js.
server.py
import os import tempfile import json import hou import hwebserver import webutils OUTPUT_DIR = os.path.join(webutils.script_file_dir(), "static", "temp") @hwebserver.urlHandler("/") def index_view(request): return hwebserver.redirect(request, "/static/index.html") @hwebserver.urlHandler("/static/output/model.gltf") def gltf_view(request): frame = float(request.GET().get("frame", 1)) # Use the GLTF ROP to write to a temporary gltf file. It will create # the .bin and any textures during the process. gltf_temp_file = temp_file_path(".gltf") hou.parm("/out/gltf1/file").set(gltf_temp_file) hou.node("/out/gltf1").render(frame_range=(frame, frame)) # Note that we could read in the JSON and adjust URIs but we don't need to. return hwebserver.fileResponse( gltf_temp_file, "application/json", delete_file=True) @hwebserver.urlHandler("/static/output", is_prefix=True) def temp_output(request): # This URL handler is used to serve the temporary .bin and texture files # for the GTLF. bin_file_path = os.path.join(OUTPUT_DIR, os.path.basename(request.path())) response = hwebserver.fileResponse( bin_file_path, "application/octet-stream", delete_file=True) return response def temp_file_path(suffix): with tempfile.NamedTemporaryFile( dir=OUTPUT_DIR, suffix=suffix) as temp_file: return temp_file.name def initialize_scene(): hou.node("/obj").createNode("geo").createNode("testgeometry_crag") hou.node("/out").createNode("gltf") def run_server(): if not os.path.exists(OUTPUT_DIR): os.makedirs(OUTPUT_DIR) initialize_scene() hwebserver.registerStaticFilesDirectory( os.path.join(webutils.script_file_dir(), "static")) hwebserver.run(8008, debug=True) if __name__ == "__main__": run_server()
static/index.html
<script src="https://cdn.babylonjs.com/viewer/babylon.viewer.js"></script> <script src="https://code.jquery.com/jquery-1.12.4.js"></script> <script src="/static/index.js"></script> <babylon id="babylon-viewer"></babylon> <br>Frame <input onchange="set_frame(parseInt(this.value, 10))" value="1" size="4">
static/index.js
var app = { frame: 1, viewer: null, }; BabylonViewer.viewerManager .getViewerPromiseById('babylon-viewer').then(function (viewer) { app.viewer = viewer; $(viewer.containerElement).css("height", "90%"); viewer.onEngineInitObservable.add(function (scene) { reload_model(); }); }); function set_frame(frame) { app.frame = frame; reload_model(); } function reload_model() { app.viewer.loadModel({url: "/static/output/model.gltf?frame=" + app.frame}); }
See also |