Code-first CAD (Code-CAD) memperlakukan geometri parametrik sebagai artefak perangkat lunak yang didefinisikan lewat kode sumber. Menguji model parametrik membutuhkan pendekatan berbeda dari pengujian unit konvensional: representasi geometri (B-Rep dan tessellated mesh) rentan mengalami regresi dimensional mikro dan artefak topologi akibat pembaruan dependensi geometric kernel (seperti OpenCASCADE) atau perubahan logika skrip.

Panduan ini merancang pipeline Continuous Integration (CI) pada runner standar GitHub Actions (headless CPU) untuk mendeteksi regresi geometri secara deterministik tanpa dependensi GPU fisik.

Arsitektur Headless Runner tanpa Dedicated GPU

Mayoritas runner CI publik tidak menyediakan GPU dedicated. Framework visualisasi dan ekspor mesh yang membutuhkan konteks OpenGL harus diarahkan ke software rasterizer melalui Mesa.

Gunakan implementasi llvmpipe melalui xvfb (X Virtual Framebuffer) untuk rendering off-screen:

sudo apt-get update && sudo apt-get install -y xvfb libgl1-mesa-glx libgl1-mesa-dri mesa-utils
export LIBGL_ALWAYS_SOFTWARE=1
export GALLIUM_DRIVER=llvmpipe

Konfigurasi ini memastikan pustaka visualisasi CAD (seperti VTK, PyVista, atau Python-OpenCASCADE) menginisialisasi konteks OpenGL secara headless tanpa menghentikan proses eksekusi pipeline dengan galat display unavailable.

Tiga Strategi Regression Testing Geometri

Pengujian regresi model parametrik bertingkat dari komputasi teringan (skalar fisik) hingga terberat (perceptual diffing).

1. Perbandingan Toleransi Floating-Point Skalar Fisik

Verifikasi properti massa dan batas spasial merupakan filter regresi pertama. Jangan gunakan asersi presisi mutlak (assert a == b) karena kernel komputasi numerik menghasilkan variasi floating-point mikro antar platform.

import pytest
import numpy as np

def test_bracket_mass_properties(generated_solid):
    # Nilai baseline tersimpan dari golden sample
    EXPECTED_VOLUME = 15420.50
    EXPECTED_BBOX = [0.0, 0.0, 0.0, 50.0, 30.0, 15.0]
    
    # Hitung properti dari B-Rep solid
    actual_volume = generated_solid.volume()
    actual_bbox = generated_solid.bounding_box().to_list()
    
    # Evaluasi dengan relative dan absolute tolerance
    assert actual_volume == pytest.approx(EXPECTED_VOLUME, rel=1e-4, abs=1e-2)
    np.testing.assert_allclose(actual_bbox, EXPECTED_BBOX, rtol=1e-4, atol=1e-3)

2. Mesh Topology Hash (Quantized Invariant)

Membandingkan file STL secara biner (misal MD5 checksum mentah) menghasilkan false positive tinggi karena urutan penulisan vertex dan index face tidak selalu stabil di berbagai build kernel. Solusinya adalah melakukan kuantisasi koordinat dan pengurutan kanonikal sebelum hashing.

import hashlib
import numpy as np

def compute_mesh_topology_hash(vertices: np.ndarray, faces: np.ndarray, precision: int = 4) -> str:
    # ponytail: sorting sederhana; upgrade ke graph isomorphism jika orientasi facet berubah
    # 1. Kuantisasi koordinat untuk membuang jitter floating-point mikro
    quantized_verts = np.round(vertices, decimals=precision)
    
    # 2. Re-index dan sort verteks secara leksikografis
    sorted_indices = np.lexsort((quantized_verts[:, 2], quantized_verts[:, 1], quantized_verts[:, 0]))
    canonical_verts = quantized_verts[sorted_indices]
    
    # 3. Hash representasi byte yang sudah dinormalisasi
    hasher = hashlib.sha256()
    hasher.update(canonical_verts.tobytes())
    hasher.update(np.sort(faces, axis=1).tobytes())
    
    return hasher.hexdigest()

3. Visual Snapshot Diffing

Digunakan untuk memvalidasi fitur kosmetik, chamfer, atau fillet yang tidak selalu terdeteksi signifikan lewat pergeseran volume skalar. Simpan gambar referensi (golden snapshot), render model dari posisi kamera orthografis statis, lalu bandingkan rasio SSIM (Structural Similarity Index):

from PIL import Image
import numpy as np

def assert_visual_match(snapshot_path: str, golden_path: str, max_diff_ratio: float = 0.005):
    img_a = np.array(Image.open(snapshot_path).convert("L"))
    img_b = np.array(Image.open(golden_path).convert("L"))
    
    assert img_a.shape == img_b.shape, "Dimensi viewport snapshot tidak cocok."
    
    # Hitung selisih mutlak piksel ternormalisasi
    diff = np.abs(img_a.astype(int) - img_b.astype(int))
    diff_ratio = np.count_nonzero(diff > 10) / img_a.size
    
    assert diff_ratio <= max_diff_ratio, f"Visual regression terdeteksi: diff {diff_ratio:.4f} > {max_diff_ratio}"

Mengeliminasi Flaky Test Lintas Arsitektur CPU

Runner GitHub Actions dapat dialokasikan pada prosesor AMD EPYC atau Intel Xeon dengan set instruksi berbeda (AVX2, AVX-512, FMA). Perbedaan evaluasi instruksi Fused Multiply-Add dapat mengubah urutan pembulatan desimal terakhir.

  • Tentukan Linear & Angular Deflection: Saat mengekspor B-Rep ke STL/Mesh, tetapkan parameter diskritisasi secara eksplisit (misal linear_deflection = 0.01, angular_deflection = 0.1). Menggunakan toleransi adaptif dinamis default kernel memicu flakiness pada mesh output.
  • Hindari Fast-Math: Jika mengompilasi modul ekstensi C++/Rust lokal untuk CAD, jangan gunakan flag -ffast-math. Flag ini melanggar asosiatifitas floating-point IEEE-754 demi performa, yang merusak determinisme geometri parametrik.
  • Isolasi Bounding Box vs Convex Hull: Gunakan Bounding Box sumbu-sejajar (AABB) dengan toleransi margin aman (epsilon), bukan Oriented Bounding Box (OBB) berbasis analisis PCA yang sensitif terhadap distribusi vertex mesh.

Konfigurasi Workflow GitHub Actions

Implementasi workflow di bawah menggabungkan eksekusi headless, dependency caching, dan eksekusi test suite parametrik.

name: Code-CAD CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  geometry-regression:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Source
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: "pip"

      - name: Install System Dependencies for Headless CAD
        run: |
          sudo apt-get update
          sudo apt-get install -y xvfb libgl1-mesa-glx libgl1-mesa-dri

      - name: Install Dependencies
        run: |
          pip install -r requirements-ci.txt

      - name: Cache Golden CAD Artifacts
        uses: actions/cache@v4
        with:
          path: tests/golden_cache
          key: cad-golden-models-${{ hashFiles('cad_specs/**') }}
          restore-keys: |
            cad-golden-models-

      - name: Execute Parametric Regression Tests
        env:
          LIBGL_ALWAYS_SOFTWARE: "1"
          GALLIUM_DRIVER: "llvmpipe"
        run: |
          xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" \
          pytest tests/test_parametric_models.py --verbose

      - name: Upload Failure Artifacts
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: failed-diff-renders
          path: tests/output_diffs/

Pertimbangan Praktis Pipeline

Implementasikan pengujian geometri bertingkat: jalankan pengecekan skalar (bounding box & volume) pada setiap commit pull request karena selesai dalam sub-detik. Visual diffing dan ekspor komparasi STEP/IGES sebaiknya dieksekusi hanya jika uji skalar lulus, mengurangi overhead runtime runner CI secara signifikan.