125 lines
3.5 KiB
Python
125 lines
3.5 KiB
Python
import asyncio
|
|
import io
|
|
import shutil
|
|
import tempfile
|
|
from distutils.dir_util import copy_tree
|
|
from pathlib import Path
|
|
|
|
from quart import Quart, abort, send_file, render_template, request
|
|
from werkzeug.utils import secure_filename
|
|
|
|
app = Quart(__name__, static_folder="static", template_folder="templates")
|
|
|
|
|
|
# allow at most these many scad processes in parallel
|
|
semaphore = asyncio.Semaphore(2)
|
|
|
|
|
|
def package_path() -> Path:
|
|
return Path(__file__).parent
|
|
|
|
|
|
class Generator:
|
|
GENERATOR_SCAD_FILE_NAME = "generator.scad"
|
|
GENERATED_STL_FILE_NAME = "generated.stl"
|
|
|
|
def __init__(self, name: str, tempdir: Path | str):
|
|
self._name = name
|
|
self._tempdir = Path(tempdir)
|
|
|
|
def _generate_scad_template(self) -> str:
|
|
return f"""
|
|
use <bottle-clip.scad>
|
|
$fn=180;
|
|
// one name tag for 0.5l Club Mate and similar bottles
|
|
bottle_clip(name="{self._name}", logo="thing-logos/fablab-cube2.dxf");
|
|
"""
|
|
|
|
def _generate_files_in_temp_dir(self):
|
|
copy_tree(str(package_path() / "openscad"), str(self._tempdir))
|
|
|
|
with open(self._tempdir / self.GENERATOR_SCAD_FILE_NAME, "w") as f:
|
|
f.write(self._generate_scad_template())
|
|
|
|
async def generate_stl(self) -> str:
|
|
self._generate_files_in_temp_dir()
|
|
|
|
openscad_path = shutil.which("openscad")
|
|
|
|
if not openscad_path:
|
|
abort(500)
|
|
|
|
proc = await asyncio.create_subprocess_exec(
|
|
openscad_path,
|
|
self.GENERATOR_SCAD_FILE_NAME,
|
|
"-o",
|
|
self.GENERATED_STL_FILE_NAME,
|
|
# "--hardwarnings",
|
|
cwd=self._tempdir,
|
|
)
|
|
|
|
await proc.wait()
|
|
|
|
if proc.returncode != 0:
|
|
abort(500)
|
|
|
|
return self.GENERATED_STL_FILE_NAME
|
|
|
|
|
|
@app.route("/generate/<name>")
|
|
async def generate_rest(name: str):
|
|
async with semaphore:
|
|
with tempfile.TemporaryDirectory(prefix="fablab-bottle-clip-generator-") as tempdir:
|
|
generator = Generator(name, tempdir)
|
|
|
|
generated_stl_file_name = await generator.generate_stl()
|
|
|
|
# to be able to use send_file with a temporary directory, we need buffer the entire file in memory
|
|
# before the context manager gets to delete the dir
|
|
bytes_io = io.BytesIO()
|
|
|
|
with open(Path(tempdir) / generated_stl_file_name, "rb") as f:
|
|
while True:
|
|
data = f.read(4096)
|
|
if not data:
|
|
break
|
|
bytes_io.write(data)
|
|
|
|
# using secure_filename allows us to send the file to the user with some safe yet reasonably
|
|
# identifiable filename
|
|
return await send_file(
|
|
bytes_io,
|
|
mimetype="model/stl",
|
|
as_attachment=True,
|
|
attachment_filename=secure_filename(f"{name}.stl")
|
|
)
|
|
|
|
|
|
# aside from the RESTful API above, we need a "traditional" HTML forms compatible end point
|
|
@app.route("/generate")
|
|
async def generate_form():
|
|
try:
|
|
name = request.args["name"]
|
|
except KeyError:
|
|
abort(400)
|
|
return
|
|
|
|
return await generate_rest(name)
|
|
|
|
|
|
@app.route("/")
|
|
async def index():
|
|
return await render_template("index.html")
|
|
|
|
|
|
async def test():
|
|
with tempfile.TemporaryDirectory() as td:
|
|
generator = Generator("testabc", td)
|
|
generated_stl_file_name = await generator.generate_stl()
|
|
shutil.copy(Path(td) / generated_stl_file_name, ".")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import asyncio
|
|
asyncio.run(test())
|