Converter User Manual

Running the converter tests

Tests are diff-based: the converter produces a deterministic OOFEM .in file which is compared against a committed reference.in.

Build the converter first (from your build directory):

cd ~/build/oofem
make converter.exec

Run all converter tests:

ctest -R test_converter

Run a single test by name:

ctest -R test_converter_3DCylinderQhull -V

The -V flag prints the full output, which is useful for diagnosing failures.

Test layout

Test directories are named <Case><Mesher> so the mesher path is obvious from the directory name (e.g. 2DPlateT3D, 3DFPZQhull). Each test lives in src/converter/tests/<TestName>/ and contains:

File

Purpose

control.in

Control file template (also the OOFEM input template)

reference.in

Committed golden output to diff against

mesh.nodes + mesh.voronoi

Qhull mesh input (qhull path)

mesh.t3d

T3D mesh input (t3d path)

mesh.sh

Shell script that runs the full mesh / random-field pipeline (informational, not invoked by ctest)

random.in (optional)

Genran control file. Present in tests whose control.in references a random.dat random field via InterpolatingFunction. mesh.sh invokes genran.exe random.in random.dat before the converter so the OOFEM-side solve can read the field. random.dat and stat.dat are gitignored - regenerate locally.

Updating a reference file

After intentionally changing converter output, regenerate the reference from the test directory:

cd src/converter/tests/3DCylinderQhull
~/build/oofem/src/converter/converter.exec control.in mesh.nodes mesh.voronoi
cp oofem.in reference.in

Then re-run the test to confirm it passes.

Running the converter

The converter needs to know which mesher produced the input. Specify it with either the --mesher (or -m) CLI flag, or with a #@mesher t3d|qhull directive in control.in. The CLI flag wins if both are present.

# T3D mesh
converter.exec --mesher t3d control.in mesh.t3d

# Qhull mesh (nodes + voronoi)
converter.exec --mesher qhull control.in mesh.nodes mesh.voronoi

# Qhull mesh (nodes + delaunay + voronoi) - legacy 3-file form
converter.exec --mesher qhull control.in mesh.nodes mesh.delaunay mesh.voronoi

If control.in already declares the mesher (e.g. all the converter tests do), the flag can be omitted:

converter.exec control.in mesh.t3d
converter.exec control.in mesh.nodes mesh.voronoi

Output is always written to oofem.in in the working directory.

Control file #@ directives

The control file is an OOFEM .in template. The converter reads it line by line. Lines that start with #@ are directives - they either configure the converter or mark an injection point for mesh-derived content, and are not written to the output. All other lines (including # comments and #% regression-check blocks) are passed through verbatim.

Parsing vs consumption

All directives go through a single parser (Grid::readControlRecords) regardless of which mesher produced the input. A directive’s applicability is a property of the consumers - only the qhull writers (give3DSMOutput, give3DTMOutput) look at #@notch / #@sphereinclusion / etc., and only the T3D writers look at #@BC / #@LOAD / etc. A directive from the “wrong” pipeline is silently ignored at write time, not rejected at parse time.

The two tables below split directives by the pipeline that consumes them. Directives in the qhull table have no effect in a T3D run, and vice versa.

Shared directives (both paths)

These are injection markers, replaced in the output by content derived from the mesh.

Directive

Purpose

#@INSERT_SETS

Replaced with set records for node/element groups referenced by BCs, loads, and export modules

#@INSERT_LIVELOADS

Replaced with NodalLoad/equivalent records generated from #@LOAD requests

#@INSERT_CROSSSECTION

Replaced with cross-section records for the active grid type

The counts line (ndofman ... nelem ... nset ...) is patched in place once the mesh is processed; it is not an #@ directive but behaves like one.

Qhull-consumed directives

Consumed by give3DSMOutput / give3DTMOutput (the qhull writers) and by qhull-pipeline setup. Geometry directives set up the region and localiser; they are consumed, not written out. Parsed but not consumed when the mesher is T3D.

Directive

Arguments

Purpose

#@grid

<type>

Selects the output generator. Available types: 3dSM, 3dTM, 2dSM, 2dTM, 2dSMTM. The 3D writers cover plain, periodic, and (3D-SM only) fibre-bearing analyses; the 2D writers cover plain SM (with optional periodicity via latticeboundary2d) and plain TM. ``2dSMTM`` writes BOTH coupled subproblems in one pass from a single control.in plus a #@tmcontrol <file> pointing to the transport template: it emits oofem.sm.in (mechanical, Delaunay) and oofem.tm.in (transport, Voronoi) with shared node numbering, so #@coupling ... neumann and #@couplingflag can cross-reference SM and TM nodes/elements directly. (A 3D staggered SMTM analysis is still expressed as two separate templates, one per subproblem, each with its own #@grid.) See Grid::resolveGridType.

#@tmcontrol

<file>

Path to the transport (TM) control template; used only with #@grid 2dSMTM. The converter reads the SM template (control.in, the file it was invoked on) and this TM template, and writes oofem.sm.in + oofem.tm.in together with shared node numbering. The TM template carries the transport engineering model, material, BCs and the {#@COUPLINGBC}-style records on its own side.

#@mesher

t3d | qhull

Declares which mesher produced the input. Lets main.C dispatch without relying on the CLI --mesher flag.

#@diam

<d>

Nominal grain diameter - must match the diam used when the mesh was generated.

#@perflag

2 <px> <py> (2D) or 3 <px> <py> <pz> (3D)

Periodicity flags per axis (0 = non-periodic, 1 = periodic). For 3dSM / 2dSM, any axis = 1 switches the writer into periodic mode (CTLNODE, lattice3Dboundary / latticeboundary2d for boundary-crossing elements). For 3dTM, any axis = 1 emits latticemt3dboundary for boundary-crossing Voronoi edges (requires the OOFEM-side Lattice3dboundary_mt element to be registered). 2D TM has no periodic OOFEM element and so cannot be made periodic.

#@thickness

<t>

Out-of-plane thickness for 2D lattice2D / latticemt2D / latticeboundary2d elements (thick field of the OOFEM input). Defaults to 1.0. Ignored for 3D writers.

#@ranint

<seed>

Random integer seed. Non-negative values are replaced with -time(NULL).

#@prism

<id> box 6 <xmin> <ymin> <zmin> <xmax> <ymax> <zmax> [refine <r>] [edgerefine <re>] [surfacerefine <rs>] [regionrefine <rr>]

Defines a 3D box-shaped region. The *refine tokens exist for mirroring the generator’s mesh.in - the converter ignores them, but keeping them in sync keeps the two files readable as a pair.

#@rect

<id> box 4 <xmin> <ymin> <xmax> <ymax>

2D analog of #@prism - axis-aligned rectangle. One of #@rect / #@disk is required as region 1 for #@grid 2dSM / 2dTM / 2dSMTM; the converter uses it for boundary tests, Voronoi-vertex projection, and the inside-domain element filter. For a circular outer domain use #@disk.

#@disk

<id> centre 2 <cx> <cy> radius <r>

2D circular domain region - the converter counterpart of the generator’s #@disk and the 2D analog of #@cylinder. Usable as region 1 for #@grid 2dSM / 2dTM / 2dSMTM. Classifies vertices by radial distance (inside / on-circle / outside) and projects the Voronoi cross-section vertices of boundary mechanical elements radially onto the circle, so transport nodes lie on the boundary with no tangential transport edge. The disc is solid; holes (annulus, multiple voids) are separate #@holedisk inclusions. Non-periodic only.

#@cylinder

<id> line 6 <x1> <y1> <z1> <x2> <y2> <z2> radius <r>

Defines a cylindrical region with axis from (x1,y1,z1) to (x2,y2,z2) and the given <r>. Used alongside (or instead of) #@prism to scope the domain for the Voronoi dual.

#@interfacecylinder

<id> line 6 <x1> <y1> <z1> <x2> <y2> <z2> radius <r> [itz <t>]

Cylindrical inclusion with ITZ halo (axis as for #@cylinder). Material classification works as for #@sphereinclusion: endpoints both inside -> inside material; endpoints straddle the radius + t/2 boundary -> interface material. The itz token is optional; if omitted, ITZ defaults to #@diam. Use #@cylinderinclusion instead when you need to specify inside/interface material ids directly.

#@fibre

<id> endpoints 6 <x1> <y1> <z1> <x2> <y2> <z2> diameter <d>

Declares a straight fibre. The converter discretises it into reinforcement nodes at intersections with matrix Voronoi cells, builds lattice3D segments along the fibre, and adds latticelink3D couplings to the surrounding matrix vertices.

#@notch

<id> box {4|6} <coords> (material <m> | delete)

Axis-aligned notch box. 2D form box 4 xmin ymin xmax ymax; 3D form box 6 xmin ymin zmin xmax ymax zmax. Two modes: - material <m> - material-reassignment mode. Matrix elements whose midpoint falls inside get crossSect <m> mat <m> instead of the default. The notch is just a region of softened/alternative material; no boundary discretisation needed on the generator side. - delete - element-deletion mode. Matrix elements with midpoint inside the box are dropped entirely (the notch is a physical void). Pair with the generator-side #@notch directive so the dual mesh has a clean cell partition along the notch surface. Delete-mode notches also trigger Voronoi-vertex projection: vertices that bound a Voronoi edge crossing the notch surface get snapped onto the nearest notch face, so cross-section polygons (3D SM) and TM nodes hug the boundary. Multiple #@notch directives may be given; the first matching one wins.

#@sphereinclusion

<id> centre 3 <x> <y> <z> radius <r> itz <t> inside <mi> interface <mif>

Spherical inclusion with ITZ halo. Effective radius is r + t/2. An element whose endpoints both fall inside gets crossSect mi mat mi; an element whose endpoints straddle the sphere boundary (one inside, one outside - the ITZ zone) gets crossSect mif mat mif. Applied after #@notch on the inherited default material. The control file is expected to declare matching latticecs / material pairs for both mi and mif. Use #@bodyload to attach a body load to any of the resulting materials.

#@diskinclusion

<id> centre 2 <cx> <cy> radius <r> itz <t> inside <mi> interface <mif> or <id> centre 2 <cx> <cy> radius <r> delete

2D analog of #@sphereinclusion - circular inclusion with ITZ halo (itz/inside/interface form). Internally stored as SphereInclusionSpec with cz = 0; the existing 3D midpoint / straddle classification works for 2D points unchanged because every 2D vertex has z = 0. With the delete keyword instead it declares a hole/void - equivalent to #@holedisk (see below).

#@holedisk

<id> centre 2 <cx> <cy> radius <r>

Circular hole (void) - a first-class HoleDisk. The same line works in the generator and converter input files (the converter spelling of #@diskinclusion ... delete). The generator seeds mechanical nodes on the hole rim plus sacrificial interior points; the converter drops everything strictly inside the hole and shifts the dual cross-section (Voronoi/transport) vertices of the rim edges radially onto the circle, leaving mechanical and transport nodes on the boundary with no tangential transport edge. Any number of holes may be declared, inside a #@rect or #@disk domain. (Densify the rim with the generator-side refine token if a coarse rim leaves stray tangential transport edges.)

#@cylinderinclusion

<id> line 6 <x1> <y1> <z1> <x2> <y2> <z2> radius <r> itz <t> inside <mi> interface <mif> [debond <s0> <s1> <matDebond>]

Straight-axis cylindrical inclusion with ITZ halo. The classification uses perpendicular distance from each endpoint to the infinite axis through the two line points; inclusion semantics match #@sphereinclusion. The optional debond <s0> <s1> <matDebond> gives interface elements whose midpoint axial position s (arclength measured from point 1 along the axis) lies in [s0, s1] the material matDebond instead of mif - e.g. a bond-free / frictionless sleeve near the loaded face of a pull-out specimen. Use-case: rebar + ITZ + matrix partitioning for corrosion-cracking and bond/pull-out studies.

#@inclusionfile

<path> itz <t> inside <mi> interface <mif>

Bulk-load inclusions from a packing file produced by src/aggregate/. Each sphere line in the file becomes a SphereInclusionSpec (semantics identical to #@sphereinclusion, sharing the directive’s itz / inside / interface); each fibre line becomes a Fibre (semantics identical to #@fibre, renumbered to avoid collisions). ellipsoid lines trigger a warning - the converter’s material-classification logic only handles spheres. Use this directive instead of dozens of inline #@sphereinclusion declarations when the packing comes from aggregate.

#@bodyload

<mat> <bc_id>

Per-material body load. Any element whose crossSect / mat resolves to <mat> gets bodyloads 1 <bc_id> appended. Decoupled from inclusion directives so each test can place its StructTemperatureLoad (or similar BC) on the material it needs - e.g. on the matrix for an eigenstrain load (Wong), or on the interface for an eigendisplacement load (corrosion cylinder). Multiple entries allowed (one per material).

#@couplingflag

(no arguments)

Toggle. When present, each emitted element gets couplingflag 1 couplingnumber <N> <ids...> appended after the polycoords block. For SM lattice3D / lattice3Dboundary (at Delaunay lines), the ids are the Voronoi-line cross-section elements; for TM latticemt3D (at Voronoi lines), they are the Delaunay-line cross-section elements. Image-side entries (outsideFlag == 1) are swapped for their periodic partner via givePeriodicElement(). Used by staggered SMTM analyses (e.g. Wong percolation) where SM and TM subproblems exchange per-element data.

#@coupling

hole <id> ltf <ltf_id> { pressure <p> | neumann tmbc <bc_id> } or inclusion <id> dirichlet ltf <ltf_id>

Hydro-mechanical coupling. Hole forms (GraFahGalWhe15 thick-walled cylinder, transport -> mechanical) act on the rim of #@holedisk <id>; records are spliced into control.in at the {#@COUPLINGBC} / {#@COUPLINGSET} placeholders, with nbc/nset bumped automatically. Rigid-body restraint is the user’s responsibility (e.g. tangential pins at three #@controlvertex nodes). - ``pressure <p>`` (Approach 1, SM-only, standalone) - for each rim mechanical node, an outward radial NodalLoad (f = p * tributary * radial) plus a single-node Set, pressure scaled by <ltf_id>. - ``neumann tmbc <bc_id>`` (staggered SMTM) - one LatticeNeumannCoupling active BC per rim node, reading the live transport pore pressure and applying it as the inward boundary traction; tmbc <bc_id> tags the dual transport rim nodes with bc 1 <bc_id> so the TM template’s BoundaryCondition <bc_id> prescribes the inner pressure. Driver coupling 3 2 1 0. Pairs with #@couplingflag + latticelinearelastic ... bio <b> for the distributed Biot term. - ``inclusion <id> dirichlet ltf <ltf_id>`` (Approach 2, displacement-driven, mechanical -> transport) - for the ITZ of #@diskinclusion <id> ... itz ...: each radial ITZ structural element (a Delaunay edge straddling the inclusion circle) has a dual transport element whose two midline nodes receive the pore pressure. The writer emits, into the TM file at the #@couplingbc / #@couplingset marker lines, one LatticeDirichletCoupling BC per midline transport node (couplingelements 2 <e1> <e2> = its two flanking radial ITZ elements, dofs 1 11 = P_f) plus a single-node Set, and bumps the TM nbc/nset. Requires #@grid 2dSMTM; the SM subproblem is written first so the BCs can reference SM element ids. Driver coupling 3 1 2 0 (SM-first, the opposite of the Neumann order - the mechanical stress must exist before the transport reads it). The ITZ eigenstrain is applied separately via #@bodyload + a StructTemperatureLoad.

#@rigidarm

<master_ctl_id> face <axis> <side> mastermask 6 <m1>..<m6> doftype 6 <d1>..<d6>

SM-only. Any inside Delaunay vertex on the specified face of the region’s bounding box (axis in {1,2,3} for x/y/z; side in {min,max}) is emitted as rigidarmnode ... master <N> mastermask ... doftype ... instead of a regular node, slaved to the controlvertex referenced by <master_ctl_id>. The master controlvertex itself is kept as a regular node. Multiple #@rigidarm directives may be given (one per face); the first matching one wins. Use-case: cantilever ends clamped as rigid bodies attached to support/load control points.

#@slaveside

<master_ctl_id> face <axis> <min|max> dofs <list> [inclusiononly]

SM-only. Like #@rigidarm but uses DT_simpleSlave (identical-value slaving) instead of rigid-arm kinematics - the slave DOF takes exactly the master’s value, with no rotation transfer. Each inside Delaunay vertex on the specified face is emitted as node ... dofidmask ... doftype <flags> mastermask <ids>, slaved to the controlvertex <master_ctl_id>. Only the DOFs listed after dofs are slaved (entries: 1=D_u, 2=D_v, 3=D_w, 4=R_u, 5=R_v, 6=R_w; for 2D lattice nodes the writer emits dofidmask 3 1 2 6 and slaves the listed subset). The optional inclusiononly keyword restricts slaving to nodes inside an inclusion (perp distance from an inclusion axis/centre < radius + itz/2) - the complement of #@nodebc ... matrixonly. Every named #@controlvertex (any id, not just the spec’s own master) is exempt from slaving and stays a regular node, so corner anchors and observer points remain free regardless of which face they sit on. Works for both 2D (#@rect) and 3D (#@prism) regions; multiple #@slaveside directives may be combined (one per face/master). Use-case: pulling one face of a non-periodic specimen uniformly in a single direction (e.g. direct-tension test where the right face follows a load-controlled master node), or slaving only the reinforcement nodes on a face so a single master’s reaction gives the total pull-out force (inclusiononly, paired with #@nodebc ... matrixonly to clamp the surrounding matrix).

#@nodebc

<bc_id> face <axis> <min|max> [matrixonly] [dofs <n> <d1..dn>]

Tags every emitted node lying on the named face plane of region 1’s bounding box (axis in {1,2,3} for x/y/z) so OOFEM applies the listed BoundaryCondition records to those nodes. Multiple directives stack - repeating with the same face adds another id; a node sitting on two requested faces (e.g. a corner) collects both. In the 3D-SM writer the tag is emitted as a full per-DOF array bc 6 b1..b6, placing <bc_id> at each constrained DOF position and 0 elsewhere (OOFEM requires one entry per DOF of a 6-DOF lattice node); dofs selects which DOFs are constrained (default: the three translations 1 2 3). The referenced BoundaryCondition should therefore be a scalar prescribedvalue record. The optional matrixonly keyword tags only matrix nodes - those outside every #@sphere/cylinderinclusion - leaving bar/interface nodes that share the same face plane free (e.g. the support face of an embedded-reinforcement pull-out specimen). (In the TM writer nodes carry a single DOF and the legacy bc 1 <id> form is used.) Skipped on nodes that are themselves slaved by #@slaveside (mixing bc with mastermask is ill-defined in OOFEM); the periodic rigid-body pin in 3D TM/SM writers is merged with #@nodebc ids on the first emitted node. Use-case: prescribing saturation on top/bottom of a transport mesh, or clamping the concrete support face (translations) of a non-periodic SM specimen, without a post-process script.

#@edgebc

region <id> bc <bc_id>

Tags every emitted boundary node lying on the edge of region ``<id>`` with bc 1 <bc_id>, applying that BoundaryCondition along the whole region boundary (unlike #@nodebc, which is limited to axis-aligned bounding-box faces, this follows the region’s true shape). Resolution uses the region’s onBoundary(x,y,tol) query, so it works for any region type - #@disk -> the outer circle, #@rect -> any of the four edges. Currently wired in the 2D TM writer (one P_f dof per node); face (#@nodebc), edge (#@edgebc) and hole-rim (#@coupling) tags merge into a single bc record per node. Use-case: holding a disk’s outer boundary at zero pore pressure (drainage) so the steady-state field of a thick-walled cylinder becomes logarithmic - without it the outer boundary is insulated and the field is uniform.

#@lumpedcapacity

<0|1>

TM-only. When 1, every emitted latticemt2D / latticemt3D / latticemt3Dboundary element line gets lumpedcapacity 1 appended, switching the element capacity matrix from the consistent (coupled) form to a TPFA-style monotone diagonal form. Required for nonlinear c(p) - e.g. unsaturated Richards-style flow with retention curves - to avoid spurious oscillations near sharp wetting fronts. For latticemt3Dboundary only the upper-left 2x2 (the two regular nodes) is lumped; the macro-gradient block on the third node has no storage term and stays zero. Default 0.

#@material_around

<ctl_id> material <m>

SM-only. Any Delaunay line with either endpoint equal to the named controlvertex gets material <m>. Precedence: #@material_around < #@notch < #@sphereinclusion / #@cylinderinclusion - later rules override earlier ones. Use-case: cantilever lattice lines attached to the support/load points pick up the elastic material rather than the default matrix material.

#@controlvertex

<id> coords {2|3} <x> <y> [<z>]

Declares a specific mesh-node location whose nearest Delaunay-vertex id is exposed at write time via the inline placeholder #@CTL<id>. Must also appear as a controlvertex entry in the generator’s mesh.in so the mesher seeds a node at that coordinate. The coord arity matches the grid’s dimension. Typical use: naming support/load/monitor nodes whose ids feed into hpc, OutputManager, BC sets, and check rules.

#@CTL<id>

(inline placeholder)

Substituted on each non-directive line with the mapped OOFEM node id (compact in non-periodic mode, raw in periodic mode). Longer ids substitute first so that e.g. #@CTL12 isn’t shadowed by #@CTL1.

#@CTLNODE

(inline placeholder, not a line directive)

Substituted with the numeric id of the periodic control node before each non-directive line is written. Use anywhere the OOFEM template needs that id (e.g. hpc 2 #@CTLNODE 2 hpcw 1 1., OutputManager tstep_all dofman_output {#@CTLNODE}, #NODE number #@CTLNODE dof 2 unknown d). Inert when perflag is fully non-periodic.

{#@COUPLINGBC}

(inline placeholder)

Replaced with the NodalLoad records generated by #@coupling (one per hole-rim mechanical node). Put it in the BC section of control.in; empty if #@coupling is absent.

{#@COUPLINGSET}

(inline placeholder)

Replaced with the single-node Set records generated by #@coupling (one per hole-rim mechanical node). Put it in the set section of control.in; empty if #@coupling is absent.

#@couplingbc

(marker line)

Used by #@coupling inclusion ... dirichlet. Replaced with the LatticeDirichletCoupling BC records (one per midline transport node) in the TM template (#@tmcontrol file). Put it in the BC section - after the materials, before the load-time functions; empty if no Dirichlet coupling.

#@couplingset

(marker line)

Used by #@coupling inclusion ... dirichlet. Replaced with the single-node Set records (one per midline transport node) in the TM template. Put it at the end of the set section; empty if no Dirichlet coupling.

#@pov

(no arguments)

Opt in to writing the auxiliary POV-Ray rendering files (*.vor.line.pov, *.vor.cross.pov, *.del.line.pov, *.del.cross.pov). Default off - POV files are not generated unless this directive is present.

#@vtk

(no arguments)

Opt in to writing the ParaView .vtu files (*.voronoielement.vtu, *.delaunayelement.matN.vtu, *.fibre.beamElement.vtu, etc.). Default off - VTK files are not generated unless this directive is present. One *.delaunayelement.matN.vtu is written per structural material declared in control.in (nmat); each element’s material is resolved on the fly from the notch/inclusion geometry, so the mesh can be shown with projected materials without running an analysis.

T3D-consumed directives

Consumed by the T3D writers (giveOutputT3d / writeT3dNodesOofem / writeT3dElemsOofem). These directives describe BCs, loads, and section data attached to named T3D entities (vertices, curves, surfaces). Parsed but not consumed when the mesher is qhull.

Directive

Arguments

Purpose

#@BC

<entType> <entID> <dofMask> <values...> [ltf <ltfID>]

Dirichlet BC on a T3D entity. Generates BoundaryCondition records and a matching set. ltf selects the OOFEM load-time function id (defaults to 1, which conventionally is the user’s ConstantFunction 1).

#@LOAD

<entType> <entID> q <q> [ltf <ltfID>]

Distributed load on a T3D entity. Emitted via #@INSERT_LIVELOADS. ltf selects the OOFEM load-time function id (defaults to 1). Distinct #@LOAD directives that target overlapping node sets must use the same ltf, since each emitted NodalLoad carries a single LTF - the last load touching a node wins.

#@DIR

<entID> <dx> <dy> <dz>

Direction vector associated with an entity (used for shell/beam orientation).

#@THICKNESS

<entID> <t>

Shell/plate thickness for an entity.

#@3DSECTION

<args>

3D section data for shell/beam elements.

#@SHELLWIDTHSCALE

<args>

Per-edge width scaling for shell elements.

#@element

<entityKind> <entityID> <elementName> <crossSect> <mat> or material <mat> <elementName>

Element-name override. Entity form (T3D writer): <entityKind> is one of vertex, curve, surface, patch, shell; edges classified to the given entity emit <elementName> instead of the writer’s hardcoded default (lattice3D, lattice3d, or lattice3Dnl depending on geometry). Edges that don’t match any directive use the default. Resolution priority for an edge that touches multiple entities: curve > surface > region. Material form (Delaunay/qhull give3DSMOutput writer, which has no T3D entities): every inside line resolving to material <mat> emits <elementName> instead of lattice3D. Since that writer already emits crossSect == mat == <mat> with the Voronoi facet as polycoords, this is exactly the record a latticecontact3d interface element expects - so #@element material 3 latticecontact3d turns the ITZ shell of a #@cylinderinclusion into node-to-node bond-contact elements (pair with a latticebondplast material and a latticecs for material 3).

#@rebar

<curveID> diameter <d> crossSect <cs> mat <m> bondCS <bcs> bondMat <bmat> [element <name>]

Declare a T3D curve as an embedded reinforcement bar. The curve must be tagged with output yes in mesh.in so T3D emits its 1D line segments. The rebar curve should use its own vertices declared independently of the patch - T3D shares mesh nodes only between entities that explicitly reference each other (e.g. a curve’s endpoint vertices, or a patch’s boundary curves), so geometric coincidence alone does not cause merging. The converter emits one rebar element per curve segment (default lattice3Dnl; override with element <name>) using crossSect/mat, plus one latticelink3D per rebar node coupling it to the nearest matrix Delaunay vertex. The link’s length is the tributary rebar-segment length at that node (interior: average of adjacent segments; end: half the single adjacent segment); dirvector is the local rebar tangent; diameter is the rebar diameter from the directive; bond cross-section and material come from bondCS/bondMat. The rebar LatticeCS should normally use shape 1 radius <r> for a circular cross-section. Use-case: discrete reinforcement in plain or textile-reinforced concrete shells with MC2010 bond-slip via latticeslip. Multiple #@rebar directives are allowed (one per bar curve).

<entType> uses the numeric codes from Grid::entityTypeFromString: 1 vertex, 2 curve, 3 surface, 5 patch, 6 shell.

2dSM writer - 2D structural mechanics

#@grid 2dSM dispatches to Grid::give2DSMOutput, which emits one lattice2D element per Delaunay edge inside the rect (or latticeboundary2d for periodic-crossing edges). The OOFEM line format is

lattice2D <id> nodes 2 <n1> <n2> crossSect <m> mat <m>
          gpCoords 2 <gx> <gy> width <w> thick <t>

with gpCoords the midpoint of the lattice element, width the length of the dual Voronoi cross-section edge, and thick the out-of-plane thickness from #@thickness. The companion test domain type is domain 2dlattice (or 2dLattice).

Periodic mode (any axis of #@perflag set) appends a CTLNODE whose coordinates are the specimen dimensions and whose DOFs hold the macro strains:

node <ctlNode> coords 2 <lx> <ly> dofidmask 3 31 32 42

Boundary-crossing Delaunay edges are emitted as

latticeboundary2d <id> nodes 3 <inside> <partner> <ctlNode>
                  crossSect <m> mat <m> gpCoords 2 <gx> <gy>
                  width <w> thick <t> location <code>

where <partner> is the periodic partner of the outside endpoint and <code> is the 1..8 compass-direction shift code consumed by Lattice2dBoundary::giveSwitches.

2dTM writer - 2D mass transport

#@grid 2dTM dispatches to Grid::give2DTMOutput. Mirrors 2dSM with Voronoi/Delaunay roles swapped: nodes are Voronoi vertices, each latticemt2D element is a Voronoi edge, the cross-section width is the length of the dual Delaunay edge:

latticemt2D <id> nodes 2 <n1> <n2> mat <m> dim 1
            thick <t> width <w> gpCoords 2 <gx> <gy>

The companion domain type is domain 2dMassLatticeTransport. 2D TM periodicity is not implemented - OOFEM has no Lattice2dBoundary_mt element. Setting #@perflag periodic with #@grid 2dTM still emits latticemt2D for inside edges; boundary-crossing edges are dropped.

Voronoi-vertex projection at boundaries

For 2D and for delete-mode 3D notches, the converter projects Voronoi vertices that bound a crossing Voronoi edge onto the nearest face of the rect / notch (Grid::project2DVoronoiVerticesToBoundaries and project3DVoronoiVerticesToNotches). This serves two purposes:

  • Transport nodes land on the surface. TM elements that cross a boundary become elements with one inside endpoint and one on-the-boundary endpoint, matching the 3D outer-boundary behaviour of Prism::modifyVoronoiCrossSection.

  • SM cross-sections are clipped at the surface. The dual Voronoi edge length used as lattice2D width reflects the part inside the specimen rather than extending past it.

Selectivity matters: only Voronoi vertices that bound a crossing edge are projected. Vertices “deep outside” (only connected to other outside vertices) keep their qhull positions and are filtered out at emission time. A blanket “clamp every outside vertex” pass would collapse far-out vertices onto the same rect corner and create coincident TM nodes.

For notches, projection only fires for #@notch ... delete boxes - material-mode notches are pure material overrides and don’t represent a physical surface, so projecting Voronoi vertices for them would distort polygons without a coherent boundary to snap to.

3D periodic mass transport - latticemt3dboundary

#@grid 3dTM with #@perflag periodic emits latticemt3dboundary elements for boundary-crossing Voronoi edges. This requires the OOFEM-side Lattice3dboundary_mt element registered under the latticemt3dboundary keyword (in src/tm/Elements/LatticeElements/). The converter test tests/3DPerQhullTM/ exercises the full pipeline.

Random-field pipeline (mesh.sh + genran)

OOFEM InterpolatingFunction records can read a regular-grid random field from random.dat. To keep the full pipeline reproducible from the test directory, tests that consume random.dat ship a random.in genran control file, and mesh.sh runs genran between the qhull and converter steps:

~/build/oofem/src/generator/generator.exec mesh.in
mv nodes.dat mesh.nodes
qvoronoi p Fv < mesh.nodes > mesh.voronoi
~/Software/genran.git/genran.exe random.in random.dat
~/build/oofem/src/converter/converter.exec control.in mesh.nodes mesh.voronoi

Both random.dat and the stat.dat summary genran writes are gitignored - they’re build artefacts, regenerated from random.in each run. See tests/3DCylinderQhull/ for the canonical example.

Unified 3dTM writer - plain and periodic

#@grid 3dTM dispatches to Grid::give3DTMOutput, which handles plain and periodic mass transport on the Voronoi dual mesh. When #@perflag has any periodic axis, the writer pins the first emitted Voronoi vertex (bc 1 1), emits latticemt3Dboundary for lines whose endpoints straddle a periodic face (with image-side endpoints swapped for their mirror partners and a location 2 ... suffix), and appends a control node with ndofs 3 dofIDmask 3 1 2 3 bc 3 1 1 2 - available in the template via #@CTLNODE. Non-periodic behaviour is unchanged (compact 1..N vertex ids, no control node, no boundary elements). Per-element material is resolved through #@notch / #@sphereinclusion / #@cylinderinclusion just like the SM writer, and #@bodyload / #@couplingflag apply symmetrically.

Adding a new analysis type

The long-term goal is that supporting a new analysis type means writing a new control.in, not touching C++. When adding a directive:

  1. Register it in the unified Grid::readControlRecords parser. The parser is shared; there is no per-pipeline parser to edit.

  2. Wire it into the writer(s) that should consume it - one of the qhull writers (give3DSMOutput / give3DTMOutput), the T3D writers, or both.

  3. Add it to the qhull-consumed or T3D-consumed table above, depending on where you wired it up. A directive consumed by both pipelines goes in a shared row - none exist today, but nothing in the parser prevents that.

Keep mesh.in and the #@prism/#@cylinder/#@fibre etc. lines in control.in mirrored so that readers can see the mesh parameters in both files. Prefer extending give3DSMOutput / give3DTMOutput over adding a new grid type.