stentfit.core.artery_geom

Attributes

_MESH_SPEC

Functions

_make_circle_points(→ numpy.ndarray)

Sample evenly-spaced points around a circle in the XY plane, at z=0.

_build_tube_mesh(→ trimesh.Trimesh)

Sweep a circular cross-section along a centreline into a tube mesh.

_add_outer_wall(→ trimesh.Trimesh)

Turn a lumen-only tube mesh into a hollow-walled tube.

straight_centreline(→ numpy.ndarray)

Build a straight centreline of the given length along the z-axis.

curved_centreline(→ numpy.ndarray)

Build a straight → arc → straight centreline, in the XZ plane.

s_bend_centreline(→ numpy.ndarray)

Build an S-shaped centreline: straight → arc right → straight → arc left → straight.

generate_straight_artery(→ trimesh.Trimesh)

Build a straight tube mesh of constant radius.

generate_curved_artery(→ trimesh.Trimesh)

Build a tube mesh of constant radius along a single-bend centreline.

generate_s_bend_artery(→ trimesh.Trimesh)

Build a tube mesh of constant radius along an S-shaped centreline.

_apply_radial_noise(→ numpy.ndarray)

Perturb a straight tube mesh's radius with a smooth, organic-looking pattern.

_compute_rmf(→ tuple[numpy.ndarray, numpy.ndarray, ...)

Propagate a rotation-minimising frame (tangent, normal, binormal) along a centreline.

_warp_coords_to_centreline(→ numpy.ndarray)

Bend a straight tube mesh's node coordinates onto an artery centreline.

_count_inverted(→ int)

Count elements that ended up inverted (negative signed volume).

mesh_artery_gmsh(→ pathlib.Path)

Mesh the artery wall as a hollow 3D solid with GMSH and write it as a 4C .yaml.

import_artery_solid(...)

Import the artery solid mesh into BeamMe, from a 4C .yaml or a CubitPy model.

assemble_beam_solid(...)

Tie the stent beam mesh to the artery solid's lumen surface and write the combined 4C input.

Module Contents

stentfit.core.artery_geom._make_circle_points(radius: float, n_circumference: int) numpy.ndarray

Sample evenly-spaced points around a circle in the XY plane, at z=0.

Parameters:
  • radius – Circle radius.

  • n_circumference – Number of points around the circle.

Returns:

(n_circumference, 3) array of points.

stentfit.core.artery_geom._build_tube_mesh(centerline: numpy.ndarray, radii: numpy.ndarray, n_circumference: int = 32, cap_ends: bool = True, noise_amplitude: float = 0.0, noise_seed: int | None = None) trimesh.Trimesh

Sweep a circular cross-section along a centreline into a tube mesh.

A local frame (tangent, normal, binormal) is propagated along the centreline by parallel-transporting the normal at each step (a rotation-minimising-frame approximation), so the cross-section doesn’t twist. If noise_amplitude is positive, a “biological” wall-roughness field is added as a fractional radius deviation: a smooth low-frequency component along the length (75% weight, mimicking natural stenosis/dilatation) plus a small-scale per-vertex component (25% weight, mimicking endothelial texture), clamped so the radius never drops below 10% of its nominal value. Both ends are capped with a triangle fan if cap_ends.

Parameters:
  • centerline(n, 3) points along the tube’s centreline.

  • radii – Per-section radius, one value per centreline point.

  • n_circumference – Number of vertices around each cross-section.

  • cap_ends – Close both ends of the tube with a triangle fan.

  • noise_amplitude – Fractional wall-roughness noise, as a fraction of the radius.

  • noise_seed – Seed for the wall noise. None draws a fresh pattern each call.

Returns:

The tube surface mesh, with normals fixed to point outward.

stentfit.core.artery_geom._add_outer_wall(inner_mesh: trimesh.Trimesh, centerline: numpy.ndarray, radii: numpy.ndarray, wall_thickness: float, n_circumference: int) trimesh.Trimesh

Turn a lumen-only tube mesh into a hollow-walled tube.

Builds a second, larger, independently-capped tube at radii + wall_thickness as the outer wall, flips inner_mesh’s normals so they face into the wall material instead of outward, and concatenates both into one mesh representing the wall’s inner and outer boundary together.

Parameters:
  • inner_mesh – The lumen surface, from _build_tube_mesh().

  • centerline(n, 3) points along the tube’s centreline, matching inner_mesh.

  • radii – Per-section lumen radius, matching inner_mesh.

  • wall_thickness – Wall thickness added to radii for the outer surface.

  • n_circumference – Number of vertices around each cross-section.

Returns:

The combined outer + inner (inverted) surface mesh.

stentfit.core.artery_geom.straight_centreline(length: float, n_points: int = 100) numpy.ndarray

Build a straight centreline of the given length along the z-axis.

Parameters:
  • length – Centreline length, in mm.

  • n_points – Number of points along the centreline.

Returns:

(n_points, 3) array of points, at x = y = 0.

stentfit.core.artery_geom.curved_centreline(length: float, bend_radius: float, bend_angle_deg: float = 45.0, n_points: int = 100) numpy.ndarray

Build a straight → arc → straight centreline, in the XZ plane.

The middle section is a circular arc of bend_radius sweeping bend_angle_deg; the two straight segments before and after it are equal length, sized so the whole centreline’s arc length adds up to length. Points are sampled at even arc-length steps along the whole path.

Parameters:
  • length – Total centreline arc length, in mm.

  • bend_radius – Radius of the circular arc, in mm.

  • bend_angle_deg – Total bend angle, in degrees.

  • n_points – Number of points along the centreline.

Raises:

ValueError – If the arc alone (bend_radius * bend_angle) is longer than length, leaving no room for the two straight segments.

Returns:

(n_points, 3) array of points.

stentfit.core.artery_geom.s_bend_centreline(length: float, bend_radius: float, bend_angle_deg: float = 30.0, n_points: int = 100) numpy.ndarray

Build an S-shaped centreline: straight → arc right → straight → arc left → straight.

Walked piecewise in 3D, tracking position and heading segment by segment, so each arc continues smoothly from where the previous segment left off. The two arcs both use bend_radius/bend_angle_deg but curve in opposite directions; the three straight segments share what’s left of length equally. Each of the 5 segments gets roughly n_points / 5 points (floored at 10), so the actual point count may come out slightly different from n_points.

Parameters:
  • length – Total centreline arc length, in mm.

  • bend_radius – Radius of each circular arc, in mm.

  • bend_angle_deg – Bend angle of each arc, in degrees.

  • n_points – Target number of points along the centreline.

Raises:

ValueError – If the two arcs alone are longer than length, leaving no room for the three straight segments.

Returns:

Array of points along the centreline.

stentfit.core.artery_geom.generate_straight_artery(radius: float = 1.5, length: float = 25.0, wall_thickness: float = 0.0, n_circumference: int = 32, n_axial: int = 100, noise_amplitude: float = 0.0, noise_seed: int | None = None) trimesh.Trimesh

Build a straight tube mesh of constant radius.

The simplest of the three artery shapes: a straight_centreline() with a uniform radius, tubed by _build_tube_mesh(). If wall_thickness is positive, a second, larger tube is added as the outer wall (_add_outer_wall()); otherwise the mesh is the lumen surface only.

Parameters:
  • radius – Lumen radius, in mm.

  • length – Artery length, in mm.

  • wall_thickness – Wall thickness, in mm. 0 builds the lumen surface only, with no separate wall.

  • n_circumference – Number of vertices around each cross-section.

  • n_axial – Number of cross-sections along the length.

  • noise_amplitude – Fractional wall-roughness noise, as a fraction of the radius.

  • noise_seed – Seed for the wall noise. None draws a fresh pattern each call.

Returns:

The artery wall mesh.

stentfit.core.artery_geom.generate_curved_artery(radius: float = 1.5, length: float = 30.0, bend_radius: float = 20.0, bend_angle_deg: float = 45.0, wall_thickness: float = 0.0, n_circumference: int = 32, n_axial: int = 100, noise_amplitude: float = 0.0, noise_seed: int | None = None) trimesh.Trimesh

Build a tube mesh of constant radius along a single-bend centreline.

Same construction as generate_straight_artery(), but tubed along a curved_centreline() (straight → arc → straight) instead of a straight line.

Parameters:
  • radius – Lumen radius, in mm.

  • length – Total artery length along the centreline, in mm.

  • bend_radius – Radius of the circular arc, in mm.

  • bend_angle_deg – Total bend angle, in degrees.

  • wall_thickness – Wall thickness, in mm. 0 builds the lumen surface only, with no separate wall.

  • n_circumference – Number of vertices around each cross-section.

  • n_axial – Number of cross-sections along the length.

  • noise_amplitude – Fractional wall-roughness noise, as a fraction of the radius.

  • noise_seed – Seed for the wall noise. None draws a fresh pattern each call.

Returns:

The artery wall mesh.

stentfit.core.artery_geom.generate_s_bend_artery(radius: float = 1.5, length: float = 40.0, bend_radius: float = 25.0, bend_angle_deg: float = 25.0, wall_thickness: float = 0.0, n_circumference: int = 32, n_axial: int = 150, noise_amplitude: float = 0.0, noise_seed: int | None = None) trimesh.Trimesh

Build a tube mesh of constant radius along an S-shaped centreline.

Same construction as generate_straight_artery(), but tubed along a s_bend_centreline() (two opposite bends). n_axial is only a target: the S-bend centreline’s actual point count can come out slightly different, so the radius array is sized to match the centreline it actually returns rather than n_axial directly.

Parameters:
  • radius – Lumen radius, in mm.

  • length – Total artery length along the centreline, in mm.

  • bend_radius – Radius of each circular arc, in mm.

  • bend_angle_deg – Bend angle of each arc, in degrees.

  • wall_thickness – Wall thickness, in mm. 0 builds the lumen surface only, with no separate wall.

  • n_circumference – Number of vertices around each cross-section.

  • n_axial – Target number of cross-sections along the length.

  • noise_amplitude – Fractional wall-roughness noise, as a fraction of the radius.

  • noise_seed – Seed for the wall noise. None draws a fresh pattern each call.

Returns:

The artery wall mesh.

stentfit.core.artery_geom._MESH_SPEC
stentfit.core.artery_geom._apply_radial_noise(coords: numpy.ndarray, length: float, amplitude: float, seed: int | None) numpy.ndarray

Perturb a straight tube mesh’s radius with a smooth, organic-looking pattern.

Sums 4 sinusoidal modes, each oscillating along both the axial (z) and circumferential (phi) directions with a random amplitude sign and phase, to build a wall-roughness field that reads as smooth and undulating rather than uniform per-vertex noise. The field is normalised to peak at 1 before scaling by amplitude, and tapered to zero within the last 10% of the length at each end, so the flat inlet/outlet caps stay flat. Only the radius changes; z is left untouched.

Parameters:
  • coords(n, 3) node coordinates of the straight tube mesh.

  • length – Tube length along z, used to scale the noise wavelengths and end taper.

  • amplitude – Fractional radial noise, as a fraction of the local radius.

  • seed – Seed for the noise pattern. None draws a fresh pattern each call.

Returns:

The perturbed coordinates.

stentfit.core.artery_geom._compute_rmf(cl: numpy.ndarray) tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]

Propagate a rotation-minimising frame (tangent, normal, binormal) along a centreline.

The tangent is the local curve direction; the normal starts perpendicular to the first tangent and is then parallel-transported along the curve (projected onto each new tangent’s perpendicular plane and renormalised), so the frame doesn’t twist. The binormal completes the frame. Same construction as _build_tube_mesh()’s frame.

Parameters:

cl(n, 3) centreline points.

Returns:

(T, N, B) — the per-point tangent, normal, and binormal, each (n, 3).

stentfit.core.artery_geom._warp_coords_to_centreline(coords: numpy.ndarray, centreline: numpy.ndarray) numpy.ndarray

Bend a straight tube mesh’s node coordinates onto an artery centreline.

Each node’s z is treated as its arc-length position along the straight tube (clamped to the centreline’s own arc range), used to interpolate that point’s position and rotation-minimising frame (_compute_rmf()) on centreline. The node’s local x/y offset is then re-expressed along the interpolated normal/binormal instead of the original straight-tube axes — the same warping convention used for the stent beam mesh, so the two stay aligned.

Parameters:
  • coords(n, 3) node coordinates of the straight tube mesh.

  • centreline – Points the straight tube is warped onto.

Returns:

The warped coordinates.

stentfit.core.artery_geom._count_inverted(coords: numpy.ndarray, conn: numpy.ndarray, mesh_type: str) int

Count elements that ended up inverted (negative signed volume).

For each element, takes the scalar triple product of the first three edge vectors from one corner node (nodes 0,1,2,3 for a tet; nodes 0,1,3,4 for a hex, treating one corner as a representative tetrahedron). A negative value means that element’s node ordering now describes a flipped, degenerate shape — a sign that a large warp or noise amplitude has folded the mesh over on itself.

Parameters:
  • coords(n, 3) node coordinates.

  • conn(n_elem, nnode) element connectivity, with 1-based node IDs.

  • mesh_type – Element type: any 'TET*' label, or 'HEX8'.

Returns:

Number of elements with negative signed volume.

stentfit.core.artery_geom.mesh_artery_gmsh(r_inner: float, r_outer: float, centreline: numpy.ndarray, out_path: str | pathlib.Path, mesh_type: str = 'TET4', element_size: float | None = None, noise_amplitude: float = 0.0, noise_seed: int | None = None, material_id: int = 1, youngs_modulus: float = 1.0, poisson_ratio: float = 0.3, density: float = 1.0) pathlib.Path

Mesh the artery wall as a hollow 3D solid with GMSH and write it as a 4C .yaml.

Builds a straight annular tube between r_inner and r_outer — unstructured tets (TET4/TET10, via an OCC cylinder-minus-cylinder boolean cut) or structured hexahedra (HEX8, via transfinite annular sectors extruded along the tube) — then classifies its boundary nodes into DSURFACE sets purely from their coordinates: the inner surface is the lumen (DSURFACE 1), and the two flat ends are the inlet/outlet (DSURFACE 2/3). Optional radial wall-roughness noise is applied, then the whole straight tube is warped onto centreline using the same frame convention as the stent warp, so the solid stays aligned with the beam mesh. Elements that end up inverted by the warp or noise are counted and reported as a warning. Writes the mesh plus a placeholder MAT_Struct_StVenantKirchhoff material as a 4C solid input file.

Parameters:
  • r_inner – Lumen (inner) radius, in mm.

  • r_outer – Outer wall radius, in mm.

  • centreline – Points the straight tube is warped onto.

  • out_path – File path the 4C .yaml solid is written to.

  • mesh_type – Element type: 'TET4', 'TET10', or 'HEX8'.

  • element_size – Target element size, in mm. None uses roughly one element across the wall thickness (r_outer - r_inner).

  • noise_amplitude – Fractional radial wall-roughness noise.

  • noise_seed – Seed for the wall noise. None draws a fresh pattern each call.

  • material_id – Material ID written into the 4C input.

  • youngs_modulus – Placeholder material Young’s modulus, in MPa.

  • poisson_ratio – Placeholder material Poisson’s ratio.

  • density – Placeholder material density.

Raises:

ValueError – If r_outer <= r_inner or r_inner <= 0, or if mesh_type isn’t one of the supported keys.

Returns:

out_path, for chaining into a caller’s own return value.

stentfit.core.artery_geom.import_artery_solid(source) tuple[beamme.four_c.input_file.InputFile, beamme.core.mesh.Mesh]

Import the artery solid mesh into BeamMe, from a 4C .yaml or a CubitPy model.

Duck-types source: a CubitPy model (detected by a cmd attribute or class name) is imported via import_cubitpy_model; anything else is treated as a path to a 4C .yaml solid (e.g. from mesh_artery_gmsh()) and imported via import_four_c_model. The CubitPy path is legacy — Cubit Coreform is no longer used in this project (no macOS build), so in practice source is always a .yaml path.

Parameters:

source – A CubitPy model, or a path to a 4C .yaml solid.

Returns:

(input_file, solid) — the BeamMe InputFile and the imported solid Mesh.

stentfit.core.artery_geom.assemble_beam_solid(input_file: beamme.four_c.input_file.InputFile, solid_mesh: beamme.core.mesh.Mesh, beam_mesh: beamme.core.mesh.Mesh, lumen_surface_index: int = 0, bc_type=None, contact_discretization: str = 'mortar', mortar_shape: str = 'line2', n_gauss_points: int = 6, output_path: str | pathlib.Path | None = None) tuple[beamme.four_c.input_file.InputFile, beamme.core.mesh.Mesh]

Tie the stent beam mesh to the artery solid’s lumen surface and write the combined 4C input.

Merges beam_mesh into solid_mesh, couples the beam elements to the solid’s lumen surface set (lumen_surface_index) via BeamMe’s mortar beam-to-solid method (add_beam_interaction_condition), and — for the meshtying variants — writes the matching header options (set_beam_to_solid_meshtying). Contact uses a different header setter, not yet wired up here. If output_path is given, dumps the assembled input file there.

Parameters:
  • input_file – BeamMe InputFile the solid (and, once merged, the beams) is added to.

  • solid_mesh – Artery solid mesh, from import_artery_solid(). Modified in place: the beam elements are merged into it.

  • beam_mesh – Warped stent beam mesh, from align().

  • lumen_surface_index – Index into the solid’s surface geometry sets for the lumen surface the beams couple to.

  • bc_type – BeamMe beam-to-solid coupling type. None defaults to tied meshtying (bme.bc.beam_to_solid_surface_meshtying).

  • contact_discretization – Mortar discretization passed to set_beam_to_solid_meshtying.

  • mortar_shape – Mortar shape function passed to set_beam_to_solid_meshtying.

  • n_gauss_points – Number of Gauss points passed to set_beam_to_solid_meshtying.

  • output_path – File path to dump the assembled 4C input to. None skips writing.

Raises:

ValueError – If solid_mesh has no surface geometry set to couple to.

Returns:

(input_file, solid_mesh) — the input file with the solid (and merged beams) added, and the combined mesh.