ca5856829b
- Use www.opendesign.com/guestfiles/get URL (no auth required) - Auto-find and symlink ODA binary after dpkg install - app.py searches multiple common install paths Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
106 lines
3.4 KiB
Python
106 lines
3.4 KiB
Python
"""DWG-to-DXF conversion microservice via ODA File Converter."""
|
|
|
|
import glob
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import uuid
|
|
|
|
from flask import Flask, request, send_file, jsonify
|
|
|
|
app = Flask(__name__)
|
|
|
|
MAX_FILE_SIZE = 100 * 1024 * 1024 # 100 MB
|
|
|
|
# ODA File Converter — try common install paths
|
|
ODA_BIN = "/usr/local/bin/ODAFileConverter"
|
|
for _p in ["/usr/local/bin/ODAFileConverter", "/usr/bin/ODAFileConverter",
|
|
"/opt/ODAFileConverter/ODAFileConverter",
|
|
"/opt/oda-file-converter/ODAFileConverter"]:
|
|
if os.path.isfile(_p):
|
|
ODA_BIN = _p
|
|
break
|
|
|
|
|
|
@app.route("/health", methods=["GET"])
|
|
def health():
|
|
"""Health check — verifies ODA File Converter is installed."""
|
|
if os.path.isfile(ODA_BIN) and os.access(ODA_BIN, os.X_OK):
|
|
return jsonify({"status": "ok", "converter": "ODAFileConverter"}), 200
|
|
return jsonify({"status": "error", "detail": "ODAFileConverter not found"}), 503
|
|
|
|
|
|
@app.route("/convert", methods=["POST"])
|
|
def convert():
|
|
"""Accept a DWG file via multipart upload, return DXF."""
|
|
if "file" not in request.files:
|
|
return jsonify({"error": "Missing 'file' field in upload."}), 400
|
|
|
|
uploaded = request.files["file"]
|
|
original_name = uploaded.filename or "input.dwg"
|
|
|
|
if not original_name.lower().endswith(".dwg"):
|
|
return jsonify({"error": "File must have .dwg extension."}), 400
|
|
|
|
safe_name = "".join(
|
|
c if c.isalnum() or c in "._-" else "_" for c in original_name
|
|
)
|
|
|
|
work_dir = os.path.join(tempfile.gettempdir(), f"dwg-{uuid.uuid4().hex}")
|
|
input_dir = os.path.join(work_dir, "in")
|
|
output_dir = os.path.join(work_dir, "out")
|
|
os.makedirs(input_dir, exist_ok=True)
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
input_path = os.path.join(input_dir, safe_name)
|
|
|
|
try:
|
|
uploaded.save(input_path)
|
|
|
|
file_size = os.path.getsize(input_path)
|
|
if file_size > MAX_FILE_SIZE:
|
|
return jsonify({"error": f"File too large ({file_size} bytes)."}), 413
|
|
|
|
# ODA File Converter: input_dir output_dir version type recurse audit
|
|
# ACAD2018 = output DXF version, DXF = output format, 0 = no recurse, 1 = audit
|
|
result = subprocess.run(
|
|
[
|
|
"xvfb-run", "--auto-servernum", "--server-args=-screen 0 1x1x24",
|
|
ODA_BIN, input_dir, output_dir, "ACAD2018", "DXF", "0", "1",
|
|
],
|
|
capture_output=True,
|
|
timeout=120,
|
|
)
|
|
|
|
# Find the output DXF file
|
|
dxf_files = glob.glob(os.path.join(output_dir, "*.dxf"))
|
|
if not dxf_files:
|
|
stderr = result.stderr.decode("utf-8", errors="replace")
|
|
stdout = result.stdout.decode("utf-8", errors="replace")
|
|
detail = stderr or stdout or "No output file produced"
|
|
return jsonify({"error": f"Conversion failed: {detail}"}), 500
|
|
|
|
dxf_path = dxf_files[0]
|
|
dxf_name = safe_name.rsplit(".", 1)[0] + ".dxf"
|
|
|
|
return send_file(
|
|
dxf_path,
|
|
mimetype="application/dxf",
|
|
as_attachment=True,
|
|
download_name=dxf_name,
|
|
)
|
|
|
|
except subprocess.TimeoutExpired:
|
|
return jsonify({"error": "Conversion timed out (120s limit)."}), 504
|
|
|
|
except Exception as e:
|
|
return jsonify({"error": f"Unexpected error: {str(e)}"}), 500
|
|
|
|
finally:
|
|
shutil.rmtree(work_dir, ignore_errors=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app.run(host="0.0.0.0", port=5001)
|