5209fd5dd0
Add dedicated dwg2dxf container (Debian slim + libredwg-tools + Flask) instead of modifying the Alpine base image. The ArchiTools API route proxies to the sidecar over Docker internal network. - dwg2dxf-api/: Dockerfile + Flask app (POST /convert, GET /health) - docker-compose.yml: dwg2dxf service, healthcheck, depends_on - route.ts: rewritten from local exec to HTTP proxy - .dockerignore: exclude sidecar from main build context Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
94 lines
2.7 KiB
Python
94 lines
2.7 KiB
Python
"""Minimal DWG-to-DXF conversion microservice via libredwg."""
|
|
|
|
import os
|
|
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
|
|
|
|
|
|
@app.route("/health", methods=["GET"])
|
|
def health():
|
|
"""Health check — verifies dwg2dxf binary is callable."""
|
|
try:
|
|
subprocess.run(["dwg2dxf", "--version"], capture_output=True, timeout=5)
|
|
return jsonify({"status": "ok", "dwg2dxf": "available"}), 200
|
|
except Exception as e:
|
|
return jsonify({"status": "error", "detail": str(e)}), 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
|
|
)
|
|
|
|
tmp_dir = os.path.join(tempfile.gettempdir(), f"dwg-{uuid.uuid4().hex}")
|
|
os.makedirs(tmp_dir, exist_ok=True)
|
|
|
|
input_path = os.path.join(tmp_dir, safe_name)
|
|
output_path = input_path.rsplit(".", 1)[0] + ".dxf"
|
|
|
|
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
|
|
|
|
result = subprocess.run(
|
|
["dwg2dxf", input_path],
|
|
capture_output=True,
|
|
timeout=120,
|
|
)
|
|
|
|
if not os.path.exists(output_path):
|
|
stderr = result.stderr.decode("utf-8", errors="replace")
|
|
return jsonify({
|
|
"error": f"Conversion failed: {stderr or 'no output file'}"
|
|
}), 500
|
|
|
|
dxf_name = safe_name.rsplit(".", 1)[0] + ".dxf"
|
|
return send_file(
|
|
output_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:
|
|
for f in os.listdir(tmp_dir):
|
|
try:
|
|
os.unlink(os.path.join(tmp_dir, f))
|
|
except OSError:
|
|
pass
|
|
try:
|
|
os.rmdir(tmp_dir)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app.run(host="0.0.0.0", port=5001)
|