Here's a complete write-up as a self-contained HTML page (no external dependencies, ready to drop into your site): ```html One Parametric Model → Tank Tests, Capytaine, FEA & CFD

One Parametric Model → Tank Model, Capytaine, FEA & CFD

A practical open-source pipeline for early-stage seastead design exploration

1. Short answers

Is a single ground-truth OpenSCAD model reasonable? Yes — at your stage it is a very good choice. OpenSCAD is text (perfect for git and for AI-assisted editing), fully parametric, and exports STL, which is exactly what your slicer, Capytaine, and OpenFOAM-family CFD all natively consume. The one weak link is FEA, which strongly prefers B-rep geometry (STEP). There is a workable bridge (FreeCAD can import .scad and export STEP), but if FEA becomes central, migrating the same parametric logic to CadQuery or build123d (Python, code-first, AI-friendly, exports STEP and STL from one source) is the natural evolution.

How to handle multi-part files? Keep one params.scad (the only file you ever edit numbers in), a library of part modules, and one assembly.scad that places parts with named transforms and doubles as the machine-readable assembly specification. Export each part headlessly with openscad -D PART="..." from a script.

Can a 1:50 plastic model tell you anything about aluminum strength? Yes — but only about stiffness, load paths, and elastic buckling, not about yield/failure. Convenient coincidence: Froude-consistent structural scaling wants Emodel = Efull/50 ≈ 70 GPa/50 ≈ 1.4 GPa, which is nearly exactly PETG/ABS (~1.6–2.3 GPa). The right role for the plastic model is to validate your FEA model; the validated FEA (re-run with aluminum properties) is what predicts strength. Details in §4.

Recommended toolset (all open source): OpenSCAD (now) or CadQuery/build123d (later) → STL for slicer & Capytaine & OpenFOAM; STEP via FreeCAD or CadQuery → Gmsh → CalculiX (PrePoMax or FreeCAD FEM) for FEA; Meshmagick for hydrostatics; OpenFOAM+olaFlow or DualSPHysics for CFD when you actually need it.

2. Structuring a single-source OpenSCAD project

2.1 Repository layout

seastead/
├── params.scad        ← THE ONLY FILE WITH NUMBERS (single source of truth)
├── lib/
│   ├── column.scad    ← module column(...) { ... }   (geometry only, no numbers)
│   ├── pontoon.scad
│   ├── deck.scad
│   └── bracing.scad
├── assembly.scad      ← places all parts; PART selector for headless export
├── export.sh          ← regenerates every deliverable (run before every test)
├── manifest.csv       ← generated: part, stl file, position, rotation (BOM/assembly spec)
└── stl/               ← generated output

2.2 params.scad — every dimension lives here

// ============ params.scad ============
// Convention: author in FULL-SCALE METERS, Z up, waterline at z=0.
$fn = 64;                    // raise to 96–128 for final export
LAMBDA = 50;                 // model scale for printing (1:50)

// Columns
N_COLS   = 4;
COL_X    = 24;               // spacing [m]
COL_Y    = 18;
COL_D    = 3.0;
COL_WALL = 0.050;            // 50 mm aluminum
DRAFT    = 12;
AIRGAP   = 6;
// Pontoon, deck, bracing, ballast, mooring... all here.
Units discipline: STL has no units. Author in meters (Capytaine, FEA and CFD all want SI), and wrap print exports in scale(1000/LAMBDA) so the slicer receives millimeters at model scale. Better still: export 3MF instead of STL for printing — 3MF carries units, so the slicer gets it right automatically. OpenSCAD exports 3MF directly.

2.3 assembly.scad — part placement is the assembly spec

include <params.scad>
use <lib/column.scad>
use <lib/deck.scad>
use <lib/pontoon.scad>

PART  = "assembly";   // overridden on CLI:  -D "PART=\"column\""
PRINT = false;        // overridden for 1:50 export: -D "PRINT=true"

// Every part gets a named frame. These transforms are THE assembly definition.
col_positions = [ for (i=[0:N_COLS-1])
  [ (i%2)*COL_X - COL_X/2, floor(i/2)*COL_Y - COL_Y/2, -DRAFT ] ];

module place(pos, rot=[0,0,0]) { translate(pos) rotate(rot) children(); }

module whole() {
  for (p = col_positions) place(p) column(d=COL_D, wall=COL_WALL,
                                           h=DRAFT+AIRGAP+DECK_T);
  place([0,0, AIRGAP]) deck();
  place([0,0,-DRAFT])  pontoon_pair();
}

scale(PRINT ? 1000/LAMBDA : 1)
  if      (PART == "assembly") whole();
  else if (PART == "column")   column(d=COL_D, wall=COL_WALL, h=DRAFT+AIRGAP+DECK_T);
  else if (PART == "deck")     deck();
  else if (PART == "pontoon")  pontoon_pair();

// Bonus: emit the assembly manifest from the same data → always in sync
for (i=[0:N_COLS-1])
  echo(str("column_", i, ",stl/column.stl,", col_positions[i], ",[0,0,0]"));

OpenSCAD has no "mates" or constraints — the place() transforms in this file are the fit-up specification, and the echo() output gives you a CSV manifest (part name → STL file → translation/rotation) that lets Blender, FreeCAD, trimesh, or a human reassemble the full structure exactly. If you want smarter, self-locating parts, look at the BOSL2 library: its attachment system (attach(), anchors, orientations) is the closest thing OpenSCAD has to CAD mates.

2.4 Headless export of every deliverable

#!/usr/bin/env bash
# export.sh — regenerate everything from the single source
set -e
PARTS="column deck pontoon assembly"
mkdir -p stl
for p in $PARTS; do
  # full scale, meters → Capytaine / FreeCAD→STEP→FEA / CFD
  openscad -o "stl/${p}_full.stl"  -D "PART=\"$p\"" -D "PRINT=false" assembly.scad
  # 1:LAMBDA, millimeters → slicer
  openscad -o "stl/${p}_1to${LAMBDA}.stl" -D "PART=\"$p\"" -D "PRINT=true" assembly.scad
done
openscad assembly.scad 2> manifest.csv   # capture echo() output
Tip: recent OpenSCAD releases include the Manifold backend, which is 10–100× faster than the old CGAL backend for complex models (openscad --backend=manifold ...). Also: design rules for clean meshes — avoid coincident/coplanar faces between parts (overlap them slightly), prefer hull()/ minkowski() for fair shapes, and verify "watertight 2-manifold" before exporting (slicers, Capytaine and snappyHexMesh all demand it). trimesh can check: python -c "import trimesh; m=trimesh.load('stl/assembly_full.stl'); print(m.is_watertight, m.volume)" — and this also gives you displacement volume for free.

3. The five pipelines

3.1 3D-printed Froude tank model you're here already

params.scad STL/3MF @ 1:λ slicer watertight print + ballast wave tank

Froude scaling (λ = 50, i.e. full-scale dimension ÷ model dimension):

QuantityFull : Model@ λ=50
Length, wave height, draftλ×50
Time, wave period√λ×7.07
Velocity√λ×7.07
Acceleration1×1
Mass / displacementλ³×125,000
Forceλ³×125,000
Pressure, stressλ×50
Mass moment of inertiaλ⁵×3.1×10⁸
Mooring stiffnessλ²×2,500

3.2 Capytaine (linear seakeeping, RAOs)

STL (full scale, meters) Capytaine (BEM) added mass, damping, excitation RAOs / animations

OpenSCAD STL is directly usable. Requirements:

import capytaine as cpt, numpy as np

body = cpt.FloatingBody(mesh="stl/assembly_full.stl", name="stead")
body.keep_immersed_part()                       # clip at z = 0
body.center_of_mass  = (0, 0, ZCG)              # from params/ballast calc
body.inertia_matrix  = body.compute_rigid_body_inertia()  # or supply ballast inertia

omegas = np.linspace(0.2, 2.0, 40)              # rad/s
probs  = [cpt.BEMProblem(body=body, omega=w, wave_direction=0.0,
                         radiating_dof=d) for w in omegas for d in body.dofs]
ds  = cpt.BEMSolver().solve_all(probs)
rao = cpt.post_pro.rao(ds)                      # response per unit wave amplitude

Visualization of wave size/period effects: animate RAO-driven rigid-body motion of your STL with matplotlib (GIF/MP4) or vedo/pyvista in 3D; or export GLB via trimesh (mesh.export('model.glb')) and animate in three.js — convenient since the results are headed for a website anyway.

3.3 FEA (structural strength)

.scad FreeCAD importCSG → STEP Gmsh (mesh) CalculiX (PrePoMax / FreeCAD FEM) stress / deflection / buckling

FEA mesherss want STEP B-rep, not STL. Two workable routes from OpenSCAD:

  1. FreeCAD bridge: FreeCAD's OpenSCAD workbench executes your .scad (needs OpenSCAD installed) and rebuilds it as B-rep solids, which export to STEP:
    # run:  freecadcmd scad2step.py assembly.scad seastead.step
    import importCSG, Import, sys
    doc    = importCSG.open(sys.argv[-2])
    solids = [o for o in doc.Objects if o.TypeId == "Part::Feature"]
    Import.export(solids, sys.argv[-1])
    Works well for moderately complex models; if a huge CSG tree chokes, convert part-by-part (you already have per-part export) and reassemble in FreeCAD using your manifest.csv.
  2. Rebuild in CadQuery/build123d (see §6) — cleaner long term.

Loads at concept stage: don't overcomplicate. Use the "design wave" method — pick the worst regular-wave phasing from Capytaine output, apply hydrostatic + wave pressure as a static load case, plus self-weight and mooring tensions. Focus on column–deck and column–pontoon joints (stress concentrations), global bending, and plate buckling. Fatigue and class-rule load cases are the naval architect's job later.

Recommended stack: Gmsh (STEP → tet mesh) → PrePoMax (friendly GUI for CalculiX; Windows) or the FreeCAD FEM workbench (CalculiX/Elmer; cross-platform) → ParaView for pretty pictures. Salome-Meca/Code_Aster is the heavyweight alternative (powerful, steep).

3.5 CFD (waves, in the computer)

STL snappyHexMesh (OpenFOAM) olaFlow / waves2Foam wave tank forces, slamming, green water

CFD is the one pipeline that natively loves STL (snappyHexMesh meshes around STL surfaces), so OpenSCAD output is fine here. Options, all open source:

  • OpenFOAM + olaFlow (or waves2Foam): the standard VoF wave-tank setup. Powerful; expect days of case setup and hours–days per run.
  • REEF3D: marine-focused, built-in regular/irregular wave generation and moorings; friendlier than raw OpenFOAM for exactly this problem.
  • DualSPHysics (SPH, GPU): excellent for slamming, green water, breaking waves; has DesignSPHysics, a FreeCAD-integrated GUI.
Sequencing advice: use Capytaine (seconds per run) to screen concepts, the tank to validate motions, and reserve CFD for the questions BEM can't answer: viscous roll damping calibration, slamming pressures, green-water-on-deck loads for the FEA. One good use: run CFD on a couple of forced-oscillation cases to get nonlinear damping coefficients, then feed those back into your Capytaine RAOs.

4. Scaled structural modeling in plastic (1:50 → aluminum)

Verdict: yes, worth doing — as a stiffness and load-path validation tool for your FEA model, not as a direct strength test. The physics:

Under Froude scaling, forces scale as λ³ and geometry as λ, so stresses scale as λ. For a structurally meaningful model you must also scale stiffness — i.e. match the Cauchy/Froude combined requirement Emodel = Efull. At λ = 50 with aluminum (E ≈ 70 GPa): target E ≈ 1.4 GPa — and PETG/ABS sit at ~1.6–2.3 GPa. This is a lucky near-match. Define the correction factor χ = λ·Emodel/Efull (≈1.4 for PETG):

Measured on modelFull-scale valueScales correctly?
Applied force / pressure×λ³ / ×λ✔ (apply Froude-scaled loads)
Deflection×λ·χ (≈ ×71)✔ with correction
Strain (gauges/DIC)×χ (≈ ×1.4)✔ with correction
Natural frequencies÷√(λ·χ) (≈ ÷8.4)✔ — and matches Froude time scaling when χ≈1
Elastic buckling modes & loadsscales consistently✔ (stiffness-driven)
Yield / ultimate failureModel is ~10× "too strong" (see below)

Why failure doesn't scale: you'd need a plastic with yield ≈ 250 MPa/50 ≈ 5 MPa; real printed plastics are 30–60 MPa and fail by layer delamination. A PETG model carries roughly an order of magnitude more scaled load than the aluminum original before failing. It will never show you the real failure load — but it will show you global deflection shapes, vibration modes, joint flexibility, and elastic buckling, which are the things FEA models usually get wrong.

Rules that make it useful

  1. Scale every structural dimension, including wall/plate thicknesses (0.05 m → 1.0 mm printed). Where printability forces thicker walls, record it and replicate the cheat in the comparison FEA model.
  2. Print tensile coupons with the same orientation/infill and measure your actual E; printed parts are anisotropic. Compute χ from measured E, not datasheet E.
  3. Apply loads as λ³-scaled forces at the correct scaled locations (whiffletree/dead weights), or test in the wave tank so the loads come for free.
  4. Measure deflections (dial gauges/laser), strains (cheap foil gauges or DIC), and natural frequencies (tap test + phone accelerometer).
  5. Build the FEA model of the plastic model first and match its measurements; only then trust the aluminum FEA. This is the whole point of the exercise.
  6. Beware creep: plastics sag under sustained load — take readings promptly, keep temperatures steady.

5. Open-source software map

ToolRole in your pipelineConsumes your OpenSCAD output?Notes
OpenSCAD (+ BOSL2)Parametric geometry (current)Text-based, AI-friendly; mesh-only output; use Manifold backend for speed
CadQuery / build123dParametric geometry (upgrade path)replaces itPython code-first CAD on OpenCASCADE; exports STEP + STL + glTF; assemblies with mates; ideal for AI generation
FreeCADB-rep bridge, FEM frontend, assemblies✔ imports .scad (OpenSCAD workbench)Exports STEP; FEM workbench drives CalculiX/Elmer; scriptable via freecadcmd
CapytaineLinear seakeeping (RAOs)✔ STL directlyPair with Meshmagick for hydrostatics/GM
MeshmagickHydrostatics from mesh✔ STL directlyDisplacement, waterplane, stability at concept level
Nemoh / BEMRosetta / HAMSBEM cross-check✔ via mesh conversionTrust-but-verify Capytaine on one case
GmshFEA/CFD meshing✔ STL (remesh) / STEP (clean)Also converts between mesh formats
CalculiX + PrePoMaxFEA solver + GUIvia STEP/INPPrePoMax is the least painful open FEA front end
Elmer / Code_Aster / FEniCSFEA alternativesvia Gmsh meshesElmer is inside FreeCAD; Code_Aster via Salome-Meca
OpenFOAM + olaFlowVoF wave CFD✔ STL natively (snappyHexMesh)Industrial standard; steep but well documented
REEF3DMarine CFD✔ STLBuilt-in irregular waves + moorings; very relevant to floating structures
DualSPHysicsSPH CFD (slamming, green water)✔ STLGPU; DesignSPHysics GUI lives inside FreeCAD
meshio / trimesh / pyvistaMesh plumbing & mass properties✔ STLFormat conversion, watertight checks, volume/inertia, glTF export for web viz
ParaView / BlenderPost-processing & presentationBlender ocean modifier + your STL makes great concept videos

6. Alternative single-source stacks

Option A — Stay OpenSCAD-centric (lowest friction)

Exactly §2 + §3: OpenSCAD → STL for print/Capytaine/CFD; FreeCAD importCSG → STEP → Gmsh → CalculiX for FEA. Cost: the FEA bridge is the fragile link. Benefit: you keep your current AI-assisted workflow untouched.

Option B — CadQuery or build123d (recommended evolution)

Same code-first, AI-friendly, git-diffable workflow — but on the OpenCASCADE B-rep kernel, so one Python file produces STEP (FEA/CFD), STL/3MF (printing, Capytaine), and glTF (web viz), plus true assemblies with constraints. Fillets/chamfers are native (no more Minkowski gymnastics). Your OpenSCAD logic ports over concept-by-concept; AI assistants are fluent in CadQuery.

import cadquery as cq
from params import *                     # same single source of numbers

def column():
    return (cq.Workplane("XY").circle(COL_D/2).extrude(COL_H)
              .faces(">Z").shell(-COL_WALL))          # thin wall, native B-rep

assy = cq.Assembly(name="seastead")
for i, (x, y) in enumerate(column_positions()):
    assy.add(column(), name=f"col_{i}", loc=cq.Location(cq.Vector(x, y, -DRAFT)))
assy.add(deck(), name="deck", loc=cq.Location(cq.Vector(0, 0, AIRGAP)))

assy.save("seastead.step")                                # → Gmsh → CalculiX, or OpenFOAM
cq.exporters.export(assy.toCompound().scale(1000/50), "seastead_1to50.stl")  # → slicer

Option C — FreeCAD-centric (GUI + scripting)

Parametric B-rep modeling, built-in FEM workbench, assembly workbench (v1.0+), Python scripting headless via freecadcmd, and DesignSPHysics for SPH waves — the most "all-in-one" open option. Downside: the GUI/topological-naming learning curve; AI help is best via its Python API rather than the GUI.

Bottom line: don't switch mid-brainstorm. Structure OpenSCAD as in §2 now; when one concept survives Capytaine + tank screening and FEA starts to matter, port that one design to CadQuery/build123d (or have FreeCAD convert it) and continue from there.

7. Gotchas checklist

  • STL has no units and no topology — fix a written convention (meters, Z-up, waterline z=0) and enforce it in params.scad comments and the export script.
  • Non-manifold meshes from coincident CSG faces break slicers, snappyHexMesh, and BEM panels — overlap parts slightly; check with trimesh.
  • Face normals must point outward for Capytaine; flipped normals give nonsense added mass.
  • Panel size vs. wavelength: don't crank $fn blindly — mesh resolution should follow the shortest wave period you simulate, not the printer's resolution.
  • OpenSCAD performance: use the Manifold backend and keep $fn low while iterating.
  • Reynolds mismatch at 1:50: viscous/roll damping is under-represented in both tank and BEM; budget a CFD or empirical correction for the final concept.
  • Version everything: params.scad + export.sh in git means every STL is reproducible; tag a commit for every tank test.
  • Printed model integrity: watertight sealing, measured material E, creep-aware test procedure.

8. What to hand the naval architect later

  • STEP assembly + BOM/manifest (from Option A or B pipeline)
  • Mass properties report: displacement, CG, inertia tensor, hydrostatics/GM (Meshmagick output)
  • RAO plots vs. wave period/direction (Capytaine), plus tank-test data and videos
  • Design-wave load cases used in FEA, and the FEA report (with the plastic-model validation evidence from §4)
  • A written list of assumptions and known limitations (viscous damping, fatigue untouched, no class rules applied yet)

That package lets the NA start engineering immediately instead of re-discovering your concept — and class-society rules (e.g., ABS/DNV floating-structure guides) get applied at that stage, not now.

``` A few extra notes beyond what's in the page: - **Quick win today:** add the `export.sh` + `manifest.csv` automation to your existing workflow — that alone solves "how do the parts fit together" in a reproducible way. - **Try the FreeCAD bridge on your current .scad files** (`importCSG`) before committing to anything; if your models convert to STEP cleanly, OpenSCAD can remain your single source for a long time. - If you want, I can sketch the equivalent `params.py` + CadQuery port of one of your existing part files so you can compare the two workflows side by side.