diff --git a/dwg2dxf-api/Dockerfile b/dwg2dxf-api/Dockerfile index e697dd9..f07e1f8 100644 --- a/dwg2dxf-api/Dockerfile +++ b/dwg2dxf-api/Dockerfile @@ -1,42 +1,27 @@ -FROM ubuntu:24.04 AS builder - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - build-essential autoconf automake libtool pkg-config \ - wget ca-certificates && \ - rm -rf /var/lib/apt/lists/* - -# Build libredwg from source (not available in apt repos) -ARG LIBREDWG_VERSION=0.13.3 -RUN wget -q "https://github.com/LibreDWG/libredwg/releases/download/${LIBREDWG_VERSION}/libredwg-${LIBREDWG_VERSION}.tar.xz" && \ - tar xf "libredwg-${LIBREDWG_VERSION}.tar.xz" && \ - cd "libredwg-${LIBREDWG_VERSION}" && \ - ./configure --prefix=/usr/local --disable-static --disable-docs && \ - make -j"$(nproc)" && \ - make install - -# --- - FROM ubuntu:24.04 -# Copy only the built binaries and libraries -COPY --from=builder /usr/local/bin/dwg2dxf /usr/local/bin/ -COPY --from=builder /usr/local/lib/libredwg* /usr/local/lib/ - -RUN ldconfig && \ - apt-get update && \ - apt-get install -y --no-install-recommends python3 python3-pip && \ +# ODA File Converter needs Qt libs + virtual framebuffer for headless mode +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + python3 python3-pip wget ca-certificates \ + xvfb libxcb-xinerama0 libxcb-icccm4 libxcb-image0 \ + libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 \ + libxcb-shape0 libxcb-xkb1 libxkbcommon0 libxkbcommon-x11-0 \ + libglib2.0-0 libgl1 libfontconfig1 libfreetype6 && \ pip3 install --no-cache-dir --break-system-packages flask && \ apt-get purge -y python3-pip && \ apt-get autoremove -y && \ rm -rf /var/lib/apt/lists/* +# Download and install ODA File Converter +RUN wget -q "https://download.opendesign.com/guestfiles/Demo/ODAFileConverter_QT6_lnxX64_8.3dll_25.12.deb" \ + -O /tmp/oda.deb && \ + dpkg -i /tmp/oda.deb || apt-get install -f -y && \ + rm /tmp/oda.deb + WORKDIR /app COPY app.py . -RUN useradd --system --no-create-home converter -USER converter - EXPOSE 5001 HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ diff --git a/dwg2dxf-api/app.py b/dwg2dxf-api/app.py index 216ee49..993da66 100644 --- a/dwg2dxf-api/app.py +++ b/dwg2dxf-api/app.py @@ -1,6 +1,8 @@ -"""Minimal DWG-to-DXF conversion microservice via libredwg.""" +"""DWG-to-DXF conversion microservice via ODA File Converter.""" +import glob import os +import shutil import subprocess import tempfile import uuid @@ -11,15 +13,16 @@ app = Flask(__name__) MAX_FILE_SIZE = 100 * 1024 * 1024 # 100 MB +# ODA File Converter installs here on Ubuntu +ODA_BIN = "/usr/bin/ODAFileConverter" + @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 + """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"]) @@ -38,11 +41,13 @@ def convert(): 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) + 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(tmp_dir, safe_name) - output_path = input_path.rsplit(".", 1)[0] + ".dxf" + input_path = os.path.join(input_dir, safe_name) try: uploaded.save(input_path) @@ -51,21 +56,30 @@ def convert(): 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( - ["dwg2dxf", input_path], + [ + "xvfb-run", "--auto-servernum", "--server-args=-screen 0 1x1x24", + ODA_BIN, input_dir, output_dir, "ACAD2018", "DXF", "0", "1", + ], capture_output=True, timeout=120, ) - if not os.path.exists(output_path): + # 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") - return jsonify({ - "error": f"Conversion failed: {stderr or 'no output file'}" - }), 500 + 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( - output_path, + dxf_path, mimetype="application/dxf", as_attachment=True, download_name=dxf_name, @@ -78,15 +92,7 @@ def convert(): 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 + shutil.rmtree(work_dir, ignore_errors=True) if __name__ == "__main__":