Pages and contents

Docs
Skills
Subagents
Lessons (pitfalls)
Research

Research · research/simulation-tools.md · Markdown

FOSS simulation and optimisation survey for cad-agent (J501 / AGX Orin enclosure)

Survey date: 2026-10-26. Licences were checked against each repo's LICENSE/COPYING file (GitHub API or GitLab API) unless marked otherwise. "Last commit" means the newest commit on the default branch at survey time. Docker tags come from the Docker Hub API on the same day.

Licence rule used throughout:

  • Importing a GPL/AGPL Python package from our MIT package does not change our source licence. But anyone who distributes the combined program must do so under the GPL, and the FSF treats an import as a combination. So GPL packages stay optional extras or run behind a process boundary.
  • Calling a GPL/AGPL program as a separate process (subprocess or Docker), and exchanging files with it, is ordinary "aggregate" use. It puts no obligation on cad-agent's licence.
  • AGPL's network clause applies only if we modify the AGPL program and offer it to users over a network.

Scroll sideways to see the full diagram →

imported

own process

served,
modified

a GPL or AGPL tool

how cad-agent
uses it

GPL binds
who ships the mix

aggregate use
no obligation

AGPL network
clause applies

Diagram source
flowchart TD
    T["a GPL or AGPL tool"] --> Q{"how cad-agent<br/>uses it"}
    Q -->|"imported"| I["GPL binds<br/>who ships the mix"]
    Q -->|"own process"| A["aggregate use<br/>no obligation"]
    Q -->|"served,<br/>modified"| N["AGPL network<br/>clause applies"]
    classDef here fill:#fffdf5,stroke:#173d3c,color:#173d3c
    classDef near fill:#eef1e4,stroke:#6f8a80,color:#173d3c
    classDef out fill:#ffe1cc,stroke:#bd3a12,color:#173d3c
    class Q here
    class T near
    class I,A,N out

Existing exposure to flag: cad-agent already does import gmsh. Gmsh is GPL-2.0-or-later. Its exception covers combination with Netgen, METIS, OpenCASCADE and ParaView, but not with arbitrary MIT code (gmsh.info/LICENSE.txt, PyPI gmsh 4.15.2, "GPLv2+"). This is the same situation as foamlib below. Document gmsh as an optional dependency, or call it via its CLI.


1. 3D topology optimisation from Python

Candidate Licence (verified) Maintenance Install Fit
pyMOTO MIT (repo) v2.0.1, 2026-05-07; last commit 2026-06-02 (releases) pip install pymoto[fast] or conda install -c aatmdelissen pymoto (README) Best fit. Voxel VoxelDomain in 2D and 3D; density filter; robust (eroded/dilated) formulation for minimum length scale; OverhangFilter (Langelaar AM filter, example); OC, MMA and GCMMA; multigrid-preconditioned CG for 3D; stress constraints; thermal and thermo-mechanical modules (ex_showcase_thermal.py, ex_thermo_mech.py); WriteToVTI to dump the density field every iteration (io.py). Modules carry their own sensitivities, so non-design regions are just a mask or mapping on the design vector.
PyTopo3D MIT (repo) v0.3.0, 2026-06-16 (PyPI) pip install pytopo3d ([gpu] extra uses CuPy) Simple 3D SIMP on voxels, with "obstacle" (non-design) regions from JSON and optional GPU. The README mentions no overhang or robust filter. A good, small fallback or reference.
dl4to Apache-2.0 (repo) Last commit 2024-11-19; not on PyPI pip install git+https://github.com/dl4to/dl4to PyTorch voxel SIMP plus deep-learning research tooling. Stale.
FEniTop GPL-3.0 (repo) Last commit 2024-11-01; pinned to dolfinx 0.7.3 Manual on a FEniCSx env Works on unstructured tets with a PDE filter, OC/MMA and MPI. Stale and pinned to an old dolfinx. GPL, so it can only run as a separate process.
dolfinx (roll our own) LGPL-3.0 (repo) v0.11.0, 2026-06; active Docker dolfinx/dolfinx:v0.11.0 (hub) or conda-forge The right base if we later need tets and MPI scale. LGPL is fine to import.
jax-fem GPL-3.0 per the LICENSE file (repo). The PyPI classifier wrongly says BSD (PyPI). v0.0.12, 2026-06; active pip Differentiable FE on GPU (JAX), with topopt examples. GPL, so it can only run as a separate process.
TopOpt_in_PETSc Unclear. GitHub detects no licence; main.cc says "The authors reserves all rights" but the repo also ships lesser.txt (LGPL text) (repo) Last commit 2026-10-17 Build against PETSc + MPI Scales to very large structured grids in C++. Avoid until the licence is clarified.
beso (CalculiX) LGPL-3.0 (repo) Last commit 2025-04-03 Python scripts + CalculiX ≥2.17 + FreeCAD BESO on tets via CalculiX .inp files. Works, but it's file-driven and needs FreeCAD.
scikit-fem topopt example BSD-3 (repo) Active (12.0.2, 2026-06) Already installed None found: docs/examples ex01–ex56 contain no topology-optimisation example. We would have to write SIMP + MMA ourselves on top of our existing P2-tet FEA.
OpenFOAM adjoint (ESI) GPL-3 v2606 Docker (see §3) adjointOptimisationFoam has topologyOptimisation tutorials for fluid topology (tree). This could later shape vents and ducts; it's not for structure.
SU2 LGPL-2.1 v8.5.0 — Has a fea_topology test case (TestCases). Niche.

Recommendation: use pyMOTO (MIT, importable) as the structural and thermal topology engine on a voxel domain built from our CadQuery solids:

  • Non-design regions: fix them to solid (bosses, insert pockets, port frames, the cooler seat) or to void (cooler, board, connector keep-outs, airflow paths).
  • Printability: add robust/density filtering for minimum feature size and OverhangFilter with the build direction = enclosure Z.
  • Size: the enclosure is 206×181×112 mm. That is about 4.2 M voxels at 1 mm and about 0.52 M at 2 mm, so optimise per part or per panel (floor, roof, ribs) at 1–2 mm with pyMOTO's multigrid CG.
  • Back to CAD: convert the result with marching cubes (scikit-image or VTK). Then verify it with our existing scikit-fem P2-tet FEA and a real slice (§5).
  • Fallback: keep PyTopo3D in reserve. If tets are ever needed, build on dolfinx (LGPL) rather than FEniTop.

2. Parametric shape/size optimisation drivers (expensive, multi-objective)

Candidate Licence Latest Notes
Optuna MIT (repo) v5.0.0, 2026-09-07 (release notes) v5 made GPSampler stable, with qLogEHVI / qLogCEHVI: batch multi-objective Bayesian optimisation with constraints. TPE is now the default multi-objective sampler. A new core trial.set_constraint() API was added. Storage is RDB or Journal, so runs are resumable and parallel, and it includes Pareto plots.
pymoo Apache-2.0 (repo) 0.6.2, 2026-06-28 NSGA-II/III, MOEA/D, constraint handling, decision making. Better suited to cheap surrogates than to expensive CFD.
Ax / BoTorch MIT (Ax, BoTorch) Ax 1.3.1 (2026-06-09), BoTorch 0.18.1 (2026-06-08) The most capable Bayesian optimisation (qNEHVI, multi-fidelity), but heavier (PyTorch).
OpenMDAO Apache-2.0 (LICENSE.txt) 3.45.1, 2026-09-10 Gradient-based MDO framework. Worth it only once we have adjoint gradients, e.g. pyMOTO or DAFoam.
scipy.optimize BSD-3 (repo) — Single-objective local optimisers (SLSQP, COBYQA, differential_evolution). Fine for 1–3 parameter refinements.

Recommendation: use Optuna 5 + GPSampler as the outer driver over CadQuery parameters (wall thickness, rib count and height, vent area and position, infill). Run it with three objectives and a temperature constraint:

  • Objectives: grams from the real slicer, compliance or max displacement from FEA, and max TTP temperature from the thermal model.
  • Constraint: trial.set_constraint with T_wall < HDT − margin.
  • Multi-fidelity: do the bulk of the search on the fast thermal model (§3B), then re-score Pareto candidates with full CHT. Add pymoo only to run NSGA-II on a fitted surrogate.

3. CFD + conjugate heat transfer

OpenFOAM: ESI/OpenCFD vs Foundation

ESI / OpenCFD (openfoam.com) Foundation (openfoam.org)
Licence GPL-3 (v2606 news) GPL-3 (COPYING)
Current v2606 (June 2026); source moved to gitlab.com/openfoam/core/openfoam v14, 14 Jul 2026 (download, OpenFOAM-14)
Steady CHT solver chtMultiRegionSimpleFoam (tutorial dir tutorials/heatTransfer/chtMultiRegionSimpleFoam) foamMultiRun with fluid + solid modules (e.g. circuitBoardCooling Allrun); single-region buoyant flow is foamRun -solver fluid
Closest tutorial cpuCabinet (tree): snappyHexMesh multi-region. Regions are domain0 (air), v_CPU (solid with 100 W scalarSemiImplicitSource on h) and v_fins (heatsink). The fan is an MRF zone (MRFProperties, ω = 209.44 rad/s). This is almost exactly our problem. circuitBoardCooling (baffles), heatExchanger (porous region + inter-region heat transfer), heatedDuct, hotWire (snappy) (tree)
Fan models fan cyclic-baffle jump BC with modes velocity / uniformVelocity / volumeFlowRate / nonDimensional (fanFvPatchField.H); fanPressure; swirlFanVelocity; fanMomentumSource fvOption: Δp(Q) fan curve applied in a cellZone (header); MRF; actuationDiskSource; explicitPorositySource fanPressureJump and fanPressure BCs (src); fvModels actuationDisk, radialActuationDisk, porosityForce, interRegionHeatTransfer, heatSource (src/fvModels); MRF (mixerVessel2DMRF)
Docker opencfd/openfoam-run:2606 (runtime only, 2026-06-23). opencfd/openfoam-default:2512 / latest has tutorials and source; no 2606 tag yet (run, default). The openfoam-docker starter script that dl.openfoam.com served moved to OpenCFD's packaging/containers repository on GitLab (the old link answers 404, checked 2026-11-01; see the wiki's Docker page). Read the script before running it, or run the image with docker run yourself. No official images after v11: the newest on Docker Hub is openfoam/openfoam11-paraview510, last updated 2024-06 (hub). v14 is shipped as Ubuntu apt packages (needs sudo) or source.

Other solvers

Candidate Licence (verified) Status CHT / fan fit
Elmer FEM GPL-2.0+; ElmerSolver library and some modules LGPL-2.1, most solver modules GPL (LICENSE.md, licence policy) release-26.2 (2026-04-28); active. Docker build repo ElmerCSC/elmerfem-docker Strong for conduction, thermal and multiphysics on gmsh tets. Fan-driven turbulent air needs more effort than OpenFOAM. A good solid-side thermal and thermo-mechanical solver.
SU2 LGPL-2.1 (LICENSE.md) v8.5.0 (2026-04-27) Has coupled_cht and solid_heat_conduction test cases (TestCases). Aero-oriented, with no snappy-style STL meshing. Not recommended here.
Code_Saturne GPL-2.0 (repo) v9.2.0; docker simvia/code_saturne:9.2.0 (2026-07-13) (hub) Industrial-grade finite-volume solver with CHT. Smaller ecosystem for STL → mesh. Second choice.
XLB (Autodesk) Apache-2.0 (LICENSE) v0.3.1 (2026-01-12); pip install "xlb[warp]" GPU LBM (Warp/JAX), differentiable. The README mentions no thermal/CHT, so it's isothermal airflow only.
lettuce MIT (repo) 0.3.0 (2026-10-06); pip install lettucecfd (the PyPI name lettuce is an unrelated package) PyTorch GPU LBM; no thermal in the README. Research grade.
OpenLB GPL-2.0 (LICENSE) 1.9r0 (2025-12-18) (download) C++ GPU LBM with thermal examples (examples/thermal: Rayleigh–Bénard, porous plate) and a "Conjugate Heat Transfer" showcase listed on the site (not inspected). Works only as a separate process.
Palabos AGPL-3.0 (COPYING) Active (2026-07) Works only as a separate process. No advantage over OpenFOAM for our case.
FluidX3D Non-commercial licence — Rejected per the brief.
DAFoam GPL-3 (LICENSE.md) v5.1.1; docker dafoam/opt-packages:v5.1.1 Discrete adjoint of OpenFOAM + OpenMDAO. A future option for gradient-based vent shape optimisation.
CfdOF (FreeCAD) LGPL-3 (repo) Active A GUI case generator for snappy and cfMesh, porous regions, buoyant flow. The README shows no multi-region CHT. A useful reference for writing snappy dictionaries.

Python case-generation helpers

  • foamlib: GPL-3.0, v1.9.0 (2026-10-19). A modern parser/writer with an async runner.
  • PyFoam: GPL, 2026.6.
  • fluidfoam: GPL-3.0-or-later.
  • ofpp: MIT, but stale (2023).

None of the good options is permissive. Recommendation:

  • Generate the dictionaries ourselves from MIT Jinja/string templates. FoamFile syntax is simple, and the cpuCabinet case is a template.
  • Read results with PyVista's OpenFOAMReader (MIT).
  • Or confine foamlib to an optional GPL extra that runs as a separate process.

ESI OpenFOAM v2606 in Docker (opencfd/openfoam-run:2606; use openfoam-default:2512 for tutorials), chtMultiRegionSimpleFoam, starting from cpuCabinet. Rationale:

  • It's a near-identical tutorial.
  • It has the richest fan options: fan BC in volumeFlowRate mode, or fanMomentumSource, both driven by the datasheet P–Q curve; MRF if the blades are modelled.
  • Official Docker images exist and don't need sudo. The Foundation route would need apt (sudo) or a source build.

Pipeline:

  1. CadQuery exports one STL per region: fluid = enclosure interior minus solids; solids = TTP/module block with a volumetric heat source, heatsink fins + base, and heatpipes as high-k solids; optionally the PLA walls as a solid region so wall temperature is resolved.
  2. blockMesh → surfaceFeatureExtract → snappyHexMesh → splitMeshRegions -cellZones → decomposePar -allRegions → mpirun -np 24 chtMultiRegionSimpleFoam -parallel. This is the cpuCabinet Allrun / Allrun.pre sequence.
  3. Model heatpipes as anisotropic or effective high-conductivity solids. This is my engineering assumption and needs calibration; no source verified.
  4. Run time for 2–5 M cells on 24 cores is unmeasured; plan on hours, not minutes.

Fast low-fidelity path for optimisation loops

  • (a) Thermal resistance network + fan operating point (milliseconds). Use the fan P–Q line from the datasheet (§6). Use system impedance from the heatsink and vent pressure-loss correlations in fluids (MIT) and fin convection from ht (MIT). This gives T_TTP and outlet air temperature per design.
  • (b) scikit-fem steady conduction in the printed walls (already in cad-agent), with Robin BCs (h, T_air) taken from (a) or from one calibration CHT run. This checks wall temperature against HDT.
  • (c) Coarse single-region OpenFOAM (~0.3–0.5 M cells, minutes; my estimate). Model the heatsink as a porous zone (explicitPorositySource, Darcy–Forchheimer fitted to the fan/heatsink Δp) with a heat source, and the fan as fanMomentumSource. Use it to rank vent layouts.
  • Calibrate (a)–(c) against 2–3 full CHT runs.
  • XLB on the RTX 3080 is an option for isothermal vent-flow screening only.

4. Post-processing, visualisation, animation

  • PyVista: MIT, v0.49.1 (2026-10-11) (repo). Plotter has open_movie / open_gif / write_frame, orbit_on_path, export_html, export_vtksz (vtk.js scene), export_gltf (three.js / model-viewer), export_vrml, export_obj and save_graphic. DataSet.streamlines / streamlines_from_source produce streamlines (plotter.py). It reads OpenFOAM cases directly. MP4/WebM encoding uses imageio-ffmpeg (BSD-2). Headless rendering uses EGL/OSMesa VTK; we have an RTX 3080 and the Docker nvidia runtime.
  • ParaView / pvpython: BSD-3 (conda-forge paraview 6.2.0). Since 6.0 there is one Linux binary that picks GLX, EGL or OSMesa at runtime (--opengl-window-backend, VTK_DEFAULT_OPENGL_WINDOW). There is no separate headless package (6.0 release notes). Get it as the tarball ParaView-6.2.0-MPI-Linux-Python3.12-x86_64.tar.gz (files/v6.2), which needs no sudo, or via conda-forge. kitware/paraview on Docker Hub has only CI tags, so there is no official runtime image (hub).
  • trame: Apache-2.0, v4.0.0 (2026-09-09) (repo). Base images are kitware/trame (e.g. uv-cuda, conda-glvnd) (hub). Use it for live interactive dashboards. For static, shareable web reports, PyVista's export_vtksz with vtk.js (BSD-3) or export_gltf with three.js is lighter.

Recommendation:

  • PyVista for all scripted images and animations: temperature slices, streamline tubes coloured by T or |U|, density-evolution flipbooks from pyMOTO WriteToVTI frames, written to MP4 (H.264) + WebM + GIF.
  • glTF/vtksz for embedded 3D viewers in an HTML report. Keep the report standard: per run, a report.html + summary.json + PNG/SVG figures + MP4.
  • ParaView tarball only for heavy, ad-hoc work.

5. Slicer CLIs for real grams and print time

All three slicers are AGPL-3.0, so we call them as subprocesses only (BambuStudio, OrcaSlicer, PrusaSlicer). Latest: Bambu Studio GitHub release v02.08.02.61; the installed snap is 02.08.04.57 (beta channel). OrcaSlicer v2.4.2. PrusaSlicer 2.9.6.

Bambu Studio (tested here, headless, works)

The working invocation:

bambu-studio --slice 0 --arrange 1 --orient 0 \
  --load-settings "machine.json;process.json" \
  --load-filaments "filament.json" \
  --outputdir OUT --export-3mf out.3mf  model.stl   # or a .3mf project
  • Machine: Bambu Lab P1S 0.4 nozzle. Process: 0.20mm Standard @BBL X1C, which lists P1S 0.4 in compatible_printers. Filament: Bambu PLA Basic @BBL P1S 0.4 nozzle. All are from /snap/bambu-studio/current/opt/bambu-studio/resources/profiles/BBL/.
  • Gotcha 1 (verified): the system JSONs use inherits. Passed raw, they leave filament_density = 0, so the slicer reports 0.00 g. You must flatten the inheritance chain (merge parent JSONs by name) before passing the files. After flattening, a 20 mm cube gave 3.75 g, 542 s (PLA, 2 walls, 20 % infill).
  • Read grams from:
    • OUT/result.json → sliced_plates[i].filaments[j].total_used_g and total_predication (s)
    • out.3mf → Metadata/slice_info.config → <filament used_g=… used_m=…> and <metadata key="prediction"/"weight">
    • plate_1.gcode header → ; total filament weight [g] : 3.75 and ; model printing time:
  • Gotcha 2: the gcode header's total filament volume [cm^3] actually printed mm³ (2974 for 2.97 cm³).
  • Gotcha 3: a "Wayland: Failed to connect to display" error is logged and is harmless. return_code was 0.
  • Gotcha 4: snap confinement. I ran in ~/snap/bambu-studio/common/. Files in host /tmp are probably not visible to the snap (not tested).
  • Other useful flags (from --help): --export-slicedata, --mstpp, --no-check, --info, --export-png, --debug 0-5, --pipe.

OrcaSlicer

It's the same CLI lineage: orca-slicer --slice 0 --load-settings "machine.json;process.json" --load-filaments f.json --outputdir OUT --export-3mf out.3mf model.stl. Flags use hyphens, --slice 0 means all plates, and the machine file comes before the process file (Orca wiki CLI, actions; third-party reference printago). Install via the AppImage or flatpak (no sudo). Not tested here.

PrusaSlicer

From the source option table (PrintConfig.cpp):

  • Options: --export-gcode|-g, --load <ini> (repeatable), --output|-o, --info, --export-3mf, --datadir, --printer-profile, --print-profile, --material-profile, --query-printer-models, --query-print-filament-profiles.
  • Example: prusa-slicer -g --load p.ini --load f.ini --load m.ini -o out.gcode model.stl. Read ; filament used [g] = … from the gcode footer (from memory, not re-verified).
  • No P1S profile was verified.

Recommendation: use the installed Bambu Studio snap as the ground truth for filament grams and print time, with flattened P1S profiles cached in the repo. Use OrcaSlicer as a cross-check.

6. Thermal reference data

Jetson AGX Orin module. Sources: datasheet DS-10662-001 v1.8, Sep 2025, mirror and Jetson Linux r36.4 power/thermal page.

  • Module power (datasheet):
    • JAO 64GB: 15 W / 30 W / 50 W, "up to 60 W"
    • JAO 32GB: 15 W / 30 W / 40 W, "up to 40 W"
    • Industrial: 15 / 35 / 60, up to 75 W
  • nvpmodel modes (r36.4):
    • 64GB: MAXN, 15 W, 30 W (default), 50 W
    • 32GB: MAXN, 15 W, 30 W (default), 40 W
  • TTP (datasheet): operating range −25 to 80 °C max for JAO; −40 to 85 °C for JAOi. Module is 100 × 87 × 16 mm.
  • SoC thresholds (r36.4): SW throttle 99 °C, HW throttle 103 °C, SW shutdown 104.5 °C, HW shutdown 105 °C. The devkit fan "cool" profile tops out at 2900 RPM.
  • Thermal Design Guide (TDG-10943-001): v1.1 is at Mouser, but it is bot-protected and I could not read Table 2-1 or its figures. Forum posts reference v1.2 and v1.3, and confirm that θ_jp is given for 32GB/64GB but "TBD" for Industrial (forum). Unverified: a third-party claim of a 60 W "MAXN SUPER" mode for the 32GB module.

Seeed J501 cooler. The reference cooler in the repo README is Seeed p-5970. The page publishes no numeric specs and it 403s to scripts. Seeed hosts a vendor datasheet under the J501 wiki (WS-RDN-601, 100×87×34 mm); the repo has a saved copy:

  • Fan: 8015 PWM fan (80×80×15 mm), ball bearing, 5 V, 0.16 A, 2300 RPM ±10 %, 27.4 dB(A).
  • Airflow: 22.90 CFM at zero static pressure. Max static pressure 1.55 mmH₂O (≈15.2 Pa); the datasheet swaps the two row labels.
  • PWM curve: 25/50/75/100 % → 700/1380/1920/2300 RPM.
  • Heat sink: "copper pipe + aluminium radiator", rated 100–120 W.
  • Heatpipe count is not stated.
  • Height caveat: the repo models the cooler top 50 mm above the seat (Z40.758 → Z90.758), but the datasheet says 34 mm. Possibly 34 mm of heatsink plus the 15 mm fan on top; measure it.
  • Other coolers: the different Mini J501 heatsink is listed at 110×100×50 mm and 14.1–55.26 CFM (openelab). It is not ours.

Printed-material limits (Bambu TDS; third-party-hosted copies; values read with pdftotext):

Material Tg (DSC) HDT 0.45 MPa HDT 1.8 MPa Source
PLA Basic (TDS V2.0) 60 °C 57 °C 54 °C TDS
PETG Basic (V2.0) 68 °C 69 °C 65 °C TDS
PETG HF (V1.0) 66 °C 69 °C 62 °C TDS

With the TTP allowed up to 80 °C and the fan exhausting warm air, PLA within a few cm of the heatsink, the exhaust path or the cooler clamp is marginal. Use PETG (or better) for parts that carry the cooler, and treat ~50 °C PLA / ~60 °C PETG sustained wall temperature under load as design limits. Those limits are my margin choice, not a datasheet value. Creep under clamp load is a separate, unmodelled risk.

Existing projects combining CAD + TO + CFD for 3D printing

There is no single mature FOSS project that does CadQuery → topology optimisation → CHT CFD → slicer. The closest building blocks:

  • FreeCAD (LGPL-2.1) + CfdOF (LGPL-3), plus FreeCAD FEM + beso (LGPL-3): a GUI-centric equivalent.
  • DAFoam (GPL-3) + OpenMDAO: adjoint CFD shape optimisation.
  • ESI adjointOptimisationFoam: fluid topology optimisation.
  • pyMOTO: thermal and structural topology optimisation with an AM overhang filter.

Reuse the ESI cpuCabinet case, pyMOTO examples and Optuna as templates, rather than adopting a framework.

Could not verify

  • TDG Table 2-1 and figures (PDF blocked).
  • Heatpipe count.
  • The fan P–Q curve beyond its two endpoints.
  • PrusaSlicer's gcode-footer key in the current version.
  • OrcaSlicer flags on this machine (not installed).
  • OpenLB's CHT showcase details.
  • CHT run times.
  • Whether ESI images include ParaView.