The mesh handed to the flow solver
32-bit in partBuilds the job for a flow study from the part and the study's settings.
The job's mesh is made with Float32Array.from(regen.mesh.positions). Everything the solver measures on the part (hole areas of openings, wall distances, the frontal area for drag and lift coefficients) is computed from those 32-bit coordinates. Face areas, normals and the bounding box are passed separately as 64-bit numbers.
/** Flow study + exact build → { job, problems, h, estimate }; check: no mesh copy. (experimental: accepted for old callers; no effect.) */
export function flowJob(st, regen, { check = false, experimental = false } = {}) {
void experimental;
const fl = st.flow || newFlow(), problems = [];
if (!regen || !(regen.exact || regen.named) || !regen.mesh || !regen.faces || !regen.stats) return { job: null, problems: [t('study.flow.problem.need_exact')] };
const gone = [], openings = fl.openings.map((o) => {
const idx = o.faces.map((f) => findFaceIndex(regen.faces, f));
if (idx.some((i) => i < 0)) gone.push(openingName(o));
return { ...o, faces: idx.filter((i) => i >= 0) };
});
if (gone.length) problems.push(t('study.flow.problem.faces_gone', { names: gone.join(', ') }));
if (fl.type === 'internal') {
if (!openings.some((o) => o.role === 'inlet')) problems.push(t('study.flow.problem.no_inlet'));
if (!openings.some((o) => o.role === 'outlet')) problems.push(t('study.flow.problem.no_outlet'));
for (const o of openings) if (!o.faces.length) problems.push(t('study.flow.problem.no_faces', { name: openingName(o) }));
const used = new Map();
for (const o of openings) for (const f of o.faces) { if (used.has(f) && used.get(f) !== o.id) problems.push(t('study.flow.problem.face_twice', { name: openingName(o) })); used.set(f, o.id); }
}
const bbox = regen.stats.bbox, h = fl.h || autoCell(fl, bbox), est = cellEstimate(fl, bbox, h);
if (est.cells > FLOW_LIMITS.gridCells) problems.push(t('study.flow.problem.grid_cells', { cells: est.cells.toLocaleString('en-US'), max: FLOW_LIMITS.gridCells.toLocaleString('en-US') }));
const fluid = fluidOf(fl.fluid);
const meshCopy = check ? null : { positions: Float32Array.from(regen.mesh.positions), indices: Uint32Array.from(regen.mesh.indices), triFace: regen.mesh.triFace ? Int32Array.from(regen.mesh.triFace) : null };
const job = {
type: fl.type, h, fluid: { name: fluid.name, density: fluid.density, viscosity: fluid.viscosity }, wall: fl.wall, model: fl.model || 'laminar', maxSteps: fl.maxSteps, tol: fl.tol,
openings: openings.map((o) => ({ id: o.id, role: o.role, faces: o.faces, spec: o.spec, value: o.value, p: o.p })),
faces: regen.faces.map((f) => ({ type: f.type, n: f.n ? f.n.slice() : null, o: f.o ? f.o.slice() : null, area: f.area })),
ext: fl.type === 'external' ? { ...boxOf(fl.ext, bbox), speed: fl.ext.speed, dir: unitv(fl.ext.dir), lift: liftDir(fl.ext), refArea: fl.ext.refArea } : null,
mesh: meshCopy, bbox: { min: bbox.min.slice(), max: bbox.max.slice() },
};
return { job, problems, h, estimate: est };
}