Files
AI Assistant ca5856829b fix(dwg): correct ODA download URL + dynamic binary path lookup
- 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>
2026-03-09 11:24:56 +02:00

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)