Skip to content

Electrode Assemblies

Electrode assemblies combine layups into a three-dimensional structure. Two families are supported:

Type Description Cell Format
WoundJellyRoll Round wound jelly roll Cylindrical
FlatWoundJellyRoll Flat (racetrack) wound jelly roll Prismatic
PunchedStack Punched and stacked electrodes Prismatic, Pouch, Flex-frame
ZFoldStack Z-fold separator with stacked electrodes Prismatic, Pouch

Jelly Rolls

Wound electrode assemblies for cylindrical and flat-wound prismatic cells.

JellyRolls

Jelly roll electrode assemblies wound around cylindrical or flat mandrels.

Classes

TapeDriver

Bases: Enum

Enumeration for tape length driver modes.

JELLY_ROLL_DRIVEN: Tape length is calculated from additional_tape_wraps TAPE_DRIVEN: Additional_tape_wraps is calculated from tape.length

WoundJellyRoll

WoundJellyRoll(laminate: Laminate, mandrel: RoundMandrel, tape: Optional[Tape] = None, additional_tape_wraps: float = 0, collector_tab_crumple_factor: float = 50.0, name: str = 'Wound Jelly Roll')

Bases: _JellyRoll

Wound jelly roll electrode assembly for cylindrical cells.

A wound jelly roll consists of electrodes and separators wound around a round mandrel in a circular spiral pattern. This configuration is commonly used in cylindrical lithium-ion batteries.

Parameters

laminate : Laminate The layup structure containing electrode and separator layers mandrel : RoundMandrel Round mandrel for cylindrical winding

Attributes

radius : float Outer radius of the wound roll in mm diameter : float Outer diameter of the wound roll in mm

Examples

from steer_opencell_design import Laminate, RoundMandrel mandrel = RoundMandrel(radius=0.001) # 1mm radius layup = Laminate(...) # Define layup structure jelly_roll = WoundJellyRoll(laminate=layup, mandrel=mandrel) print(f"Outer diameter: {jelly_roll.diameter} mm")

Initialize wound jelly roll electrode assembly.

Parameters

laminate : Laminate The layup structure to be wound mandrel : RoundMandrel Round mandrel for cylindrical winding

Raises

TypeError If mandrel is not RoundMandrel or laminate is not Laminate

Attributes
radius property writable
radius: float

Return the outer radius of the wound jelly roll in mm.

Returns

float Outer radius in millimeters, rounded to 2 decimal places

radius_hard_range property
radius_hard_range: Tuple[float, float]

Return the hard radius range (min, max) of the wound jelly roll in mm.

diameter property writable
diameter: float

Return the outer diameter of the wound jelly roll in mm.

diameter_range property
diameter_range: Tuple[float, float]

Return the diameter range (min, max) of the wound jelly roll in mm.

diameter_hard_range property
diameter_hard_range: Tuple[float, float]

Return the hard diameter range (min, max) of the wound jelly roll in mm.

Methods:
from_flat_jelly_roll classmethod
from_flat_jelly_roll(flat_jelly_roll: FlatWoundJellyRoll) -> WoundJellyRoll

Create a WoundJellyRoll from an existing FlatWoundJellyRoll.

This method takes a flat wound jelly roll and creates a cylindrical wound jelly roll using the same layup structure. A round mandrel is automatically created based on the flat mandrel's properties.

Parameters

flat_jelly_roll : FlatWoundJellyRoll The flat wound jelly roll to convert from

Returns

WoundJellyRoll A new wound jelly roll with the same layup as the flat jelly roll

Raises

TypeError If flat_jelly_roll is not a FlatWoundJellyRoll instance

Examples

flat_roll = FlatWoundJellyRoll(layup, flat_mandrel) wound_roll = WoundJellyRoll.from_flat_jelly_roll(flat_roll)

FlatWoundJellyRoll

FlatWoundJellyRoll(laminate: Laminate, mandrel: FlatMandrel, tape: Optional[Tape] = None, additional_tape_wraps: float = 0, collector_tab_crumple_factor: float = 50.0, name: str = 'Flat Wound Jelly Roll', cathode_notch_alignment_position: Optional[float] = None, anode_notch_alignment_position: Optional[float] = None)

Bases: _JellyRoll

Flat wound jelly roll electrode assembly for prismatic cells.

A flat wound jelly roll consists of electrodes and separators wound around a flat mandrel in a racetrack spiral pattern. This configuration is commonly used in prismatic and pouch lithium-ion batteries and includes hot-pressing simulation.

Parameters

laminate : Laminate The layup structure containing electrode and separator layers mandrel : FlatMandrel Flat mandrel for racetrack winding

Attributes

thickness : float Overall thickness of the flat wound roll in mm width : float Overall width of the flat wound roll in mm pressed_radius : float Mandrel radius after hot pressing in mm pressed_straight_length : float Mandrel straight length after hot pressing in mm

Examples

from steer_opencell_design import Laminate, FlatMandrel mandrel = FlatMandrel(radius=0.002, straight_length=0.050) # 2mm radius, 50mm straight layup = Laminate(...) # Define layup structure jelly_roll = FlatWoundJellyRoll(laminate=layup, mandrel=mandrel) print(f"Dimensions: {jelly_roll.width} x {jelly_roll.thickness} mm")

Initialize flat wound jelly roll electrode assembly.

Parameters

laminate : Laminate The layup structure to be wound mandrel : FlatMandrel Flat mandrel for racetrack winding cathode_notch_alignment_position : float, optional Cathode notch-center position in mm from the left edge of the unrotated pressed mandrel's straight racetrack section. The complete tab must lie on that straight section. anode_notch_alignment_position : float, optional Anode notch-center position in mm from the same straight-section edge. Cathode/anode transverse or longitudinal arrangement is controlled by laminate.electrode_orientation.

Raises

TypeError If mandrel is not FlatMandrel or laminate is not Laminate

Attributes
pressed_radius property
pressed_radius: float

Return the pressed mandrel radius in mm.

Returns

float Pressed mandrel radius in millimeters, rounded to 2 decimal places

pressed_straight_length property
pressed_straight_length: float

Return the pressed mandrel straight length in mm.

cathode_notch_alignment_position property writable
cathode_notch_alignment_position: Optional[float]

Return the cathode notch center from the straight section's left edge.

anode_notch_alignment_position property writable
anode_notch_alignment_position: Optional[float]

Return the anode notch center from the straight section's left edge.

notches_aligned property
notches_aligned: bool

Return whether BOTH electrodes' notches are aligned.

A cell that welds one tab stack per electrode -- a pouch -- needs both, so this is the whole-roll predicate rather than a per-electrode one. Distinct from :meth:can_align_notches, which asks whether an electrode could be aligned; this asks whether it currently is.

footprint_datum_xy property
footprint_datum_xy: Tuple[float, float]

Return the (x, y) the footprint is centered on, in meters.

foil_extent_y property
foil_extent_y: float

Return the wound roll's y extent, in meters.

The base implementation measures the collectors' own foil outlines, which for a roll describe the UNWOUND sheet: its tabs stand at full cut height in a frame the wound cell never occupies. Sizing an enclosure from that inflates it (a 4 mm clipped tab grew the pouch by 3 mm over the roll it holds). The roll's own height is the answer, and it already accounts for the crumpled tabs.

cathode_notch_alignment_position_range property
cathode_notch_alignment_position_range: Tuple[float, float]

Return the valid cathode notch alignment position range in mm.

anode_notch_alignment_position_range property
anode_notch_alignment_position_range: Tuple[float, float]

Return the valid anode notch alignment position range in mm.

thickness_aware_notch_data property
thickness_aware_notch_data: Dict[str, Dict[str, Any]]

Return configured notch centers, spacings, and gaps for each electrode.

thickness property writable
thickness: float

Return the overall jelly roll thickness in millimeters.

width property writable
width: float

Return the overall jelly roll width in millimeters.

thickness_range property
thickness_range: Tuple[float, float]

Return the thickness range (min, max) of the flat wound jelly roll in mm.

width_range property
width_range: Tuple[float, float]

Return the width range (min, max) of the flat wound jelly roll in mm.

Methods:
from_round_jelly_roll classmethod
from_round_jelly_roll(round_jelly_roll: WoundJellyRoll, width: float = None) -> FlatWoundJellyRoll

Create a FlatWoundJellyRoll from an existing WoundJellyRoll.

This method takes a wound jelly roll and creates a flat wound jelly roll using the same layup structure. A flat mandrel is automatically created based on the round mandrel's properties.

Parameters

round_jelly_roll : WoundJellyRoll The wound jelly roll to convert from width : float, optional The width of the flat mandrel in mm. If not provided, defaults to diameter * 11 (creating a flat mandrel with a straight section of 10x the diameter).

Returns

FlatWoundJellyRoll A new flat wound jelly roll with the same layup as the wound jelly roll

Raises

TypeError If round_jelly_roll is not a WoundJellyRoll instance

Examples

wound_roll = WoundJellyRoll(layup, round_mandrel) flat_roll = FlatWoundJellyRoll.from_round_jelly_roll(wound_roll) flat_roll_custom = FlatWoundJellyRoll.from_round_jelly_roll(wound_roll, width=50)

convert_to_aligned_notches
convert_to_aligned_notches(position: Optional[float] = None, tab_width: Optional[float] = None, electrodes: Union[str, Iterable[str]] = ('cathode', 'anode')) -> Dict[str, Dict[str, Any]]

Give the named electrodes notched collectors with aligned notches.

Replaces each electrode's current collector with the equivalent NotchedCurrentCollector (via the matching from_* classmethod) and then aligns its notches, in a single recalculation. Electrodes that already carry a notched collector keep it, so repeated calls are safe.

Parameters

position : float, optional Notch-center position in mm from the left edge of the pressed mandrel's straight racetrack section. Defaults to the center of that section, which is the one position that is valid whenever the tab fits at all. A cell knows where its terminal sits and should pass that instead when the physical location matters. tab_width : float, optional Tab width in mm applied to every converted collector. Defaults to whatever the collector already carries -- note that NotchedCurrentCollector.from_tabless starts at 50 mm, which a short straight section may not accommodate. electrodes : str or iterable of str, default ("cathode", "anode") Which electrodes to convert. Pass a single name to convert one.

Returns

Dict[str, Dict[str, Any]] thickness_aware_notch_data for the resulting configuration.

Raises

ValueError If the requested (or default) tab width cannot be aligned anywhere on this roll's straight section, or if an explicit position falls outside the valid range. Nothing is mutated in either case.

can_align_notches
can_align_notches(electrode: str) -> bool

Return whether an electrode's collector can carry aligned notches.

Alignment needs discrete tabs to place, so it is available only where the electrode carries a NotchedCurrentCollector that is not a TablessCurrentCollector. Callers deciding whether to offer alignment should use this rather than whether a position happens to be set, which is a different question.

Parameters

electrode : str 'cathode' or 'anode'.

default_notch_alignment_position
default_notch_alignment_position(electrode: str) -> float

Return the position in mm to align an electrode's notches at.

The midpoint of the straight racetrack section: the one position that is valid whenever the tab fits at all, and the furthest from both tangents.

Raises

TypeError If the electrode's collector cannot carry aligned notches.

notch_alignment_center_x
notch_alignment_center_x(electrode: str) -> float

Return the x of an electrode's aligned notch stack, in mm.

The physical coordinate in the roll's own frame -- which is what a cell needs to put a terminal on the tab stack. Not to be confused with <electrode>_notch_alignment_position, which is measured along the straight racetrack section, nor with the collector's tab_center_positions, which are distances along the UNWOUND foil and have no meaning as a position in the wound cell.

Raises

TypeError If the electrode's collector cannot carry aligned notches. ValueError If the electrode's notches are not currently aligned.

tab_stack_x
tab_stack_x(electrode_name: str) -> float

Return the x of an electrode's tab stack, in meters, in the roll's frame.

tab_extends_positive_y
tab_extends_positive_y(electrode_name: str) -> bool

Return whether an electrode's tab leaves the footprint toward +y.

Read off the built geometry rather than a cathode-up convention: a Laminate expresses electrode orientation by flipping collector coordinates, so the coordinates are the only reliable answer.

tab_tip_y
tab_tip_y(electrode_name: str) -> float

Return the y a notch stack actually reaches, in meters.

Winding crumples an integral tab, so the stack stands 1 - collector_tab_crumple_factor of its cut height off the foil edge -- the same height _calculate_notch_stack_top_down_coords draws it at. A cell welding a terminal to this stack has to meet it where it physically ends, not where an uncrumpled tab of that height would have.

plot_notch_alignment
plot_notch_alignment(layered: bool = False, **kwargs: Any) -> Figure

Plot the flat-wound cross-section with aligned notch-center markers.

Markers should form one constant-x stack for each configured electrode.


Stacks

Stacked electrode assemblies for prismatic, pouch, and flex-frame cells.

Stacks

Stacked electrode assembly configurations (Z-fold and punched).

Classes

ZFoldStack

ZFoldStack(layup: ZFoldMonoLayer, n_layers: int, additional_separator_wraps: float = 1, name: str = 'ZFoldStack')

Bases: _Stack

Z-Fold stack electrode assembly.

Accepts a ZFoldMonoLayer layup representing stacked sheets in a Z-fold configuration. Additional separator wraps provide extra insulation around the stack.

Parameters

layup : ZFoldMonoLayer The Z-fold layup configuration n_layers : int Number of electrode layers in the stack additional_separator_wraps : float, optional Number of additional separator wraps around the stack, by default 1

Attributes
additional_separator_wraps property writable
additional_separator_wraps: int

Return the number of additional separator wraps.

additional_separator_wraps_range property
additional_separator_wraps_range: Tuple[int, int]

Return the valid range for additional separator wraps as a tuple (min, max).

PunchedStack

PunchedStack(layup: MonoLayer, n_layers: int, name: str = 'PunchedStack')

Bases: _Stack

Punched mono-layer stack electrode assembly.

Accepts a MonoLayer layup representing stacked sheets in a mono-layer configuration. This configuration is typically used with punched current collectors.

Parameters

layup : MonoLayer The mono-layer layup configuration n_layers : int Number of electrode layers in the stack


Tape

Termination tape used to secure wound assemblies.

Tape

Termination tape for wound electrode assemblies.

Classes

Tape

Tape(material: TapeMaterial, thickness: float, width: float = None, length: float = None, datum: Tuple[float, float, float] = (0.0, 0.0, 0.0), name: str = 'Tape')

Bases: CoordinateMixin, DatumMixin, ValidationMixin, DunderMixin, PlotterMixin, PropagationMixin, SerializerMixin

Adhesive tape used to secure the outer wraps of wound jelly roll assemblies. Manages tape geometry (width, thickness) and material properties.

Initialize an object that represents insulating tape

Parameters

material : TapeMaterial Material of the tape. thickness : float Thickness of the tape in um. width : float, optional Width of the tape in mm. If None, bulk properties won't be calculated until set. length : float, optional Length of the tape in mm. If None, bulk properties won't be calculated until set. datum : Tuple[float, float, float], optional Reference point (x, y, z) in mm. Defaults to (0.0, 0.0, 0.0). name : str, optional Name of the tape. Defaults to 'Tape'.

Attributes
areal_cost_range property
areal_cost_range: Tuple[float, float]

Get the allowable areal cost range in $/m².

cost property
cost: float

Get the total tape cost in $.

mass property
mass: float

Get the total tape mass in g.

area property
area: float

Get the total tape area in cm².

areal_cost property writable
areal_cost: float

Get the areal cost of the tape in $/m².

name property writable
name: str

Get the tape name.

length property writable
length: float

Get the tape length in mm.

length_range property
length_range

Get the allowable tape length range in mm.

width property writable
width: float

Get the tape width in mm.

width_range property
width_range

Get the allowable tape width range in mm.

material property writable
material: TapeMaterial

Get the tape material.

thickness property writable
thickness

Get the tape thickness in µm.

thickness_range property
thickness_range

Get the allowable tape thickness range in µm.


Winding Equipment

Mandrel geometry for jelly roll winding.

WindingEquipment

Winding mandrel definitions for jelly roll electrode assemblies.

Classes

RoundMandrel

RoundMandrel(diameter: float, length: float, datum: Tuple[float, float, float] = (0, 0, 0), material=None, name='Mandrel')

Bases: _Mandrel

Cylindrical mandrel for winding round jelly rolls. Defined by diameter and length.

Initialize a round (cylindrical) mandrel.

Parameters

diameter : float Mandrel outer diameter in mm. length : float Mandrel length in mm. datum : Tuple[float, float, float], optional Reference position (x, y, z) in mm. material : CurrentCollectorMaterial, optional Mandrel material. name : str, optional Display name.

Attributes
radius property writable
radius: float

Return the mandrel radius in mm.

radius_range property
radius_range: Tuple[float, float]

Return the mandrel radius range as a tuple (min, max) in mm.

diameter property writable
diameter: float

Return the mandrel diameter in mm.

diameter_range property
diameter_range: Tuple[float, float]

Return the mandrel diameter range as a tuple (min, max) in mm.

FlatMandrel

FlatMandrel(length: float, width: float, height: float, datum: Tuple[float, float, float] = (0, 0, 0), material: CurrentCollectorMaterial = None, name: str = 'Flat Mandrel')

Bases: _Mandrel

Flat (racetrack) mandrel for winding flat jelly rolls. Defined by width, height, and length.

Initialize a flat (racetrack) mandrel.

Parameters

length : float Mandrel length in mm (along the winding axis). width : float Mandrel width in mm (total racetrack width). height : float Mandrel height in mm (total racetrack height, defines end radius). datum : Tuple[float, float, float], optional Reference position (x, y, z) in mm. material : CurrentCollectorMaterial, optional Mandrel material. name : str, optional Display name.

Attributes
radius property writable
radius: float

Return the mandrel radius (half of height) in mm.

straight_length property
straight_length: float

Return the straight segment length in mm.

width property writable
width: float

Return the mandrel width in mm.

width_range property
width_range: Tuple[float, float]

Return the mandrel width range as a tuple (min, max) in mm.

width_hard property
width_hard: Tuple[float, float]

Return the mandrel width range as a tuple (min, max) in mm.

height property writable
height: float

Return the mandrel height in mm.

height_range property
height_range: Tuple[float, float]

Return the mandrel height range as a tuple (min, max) in mm.


Spiral Utilities

Low-level functions for spiral geometry calculations.

SpiralUtils

Static utility methods for spiral and racetrack geometry calculations used in jelly roll winding.

Classes

SpiralCalculator

Utility class providing static methods for spiral and racetrack geometry calculations. Computes variable-thickness spirals, builds component-level spiral coordinates, and handles racetrack (flat-wound) geometry for FlatWoundJellyRoll assemblies.

Methods:
aligned_positions_at_x staticmethod
aligned_positions_at_x(spiral: ndarray, target_x: float, tab_width: float = 0.0) -> ndarray

Return two unwrapped centers per complete turn at a fixed x-coordinate.

Length inputs and outputs use meters. target_x is expressed in the supplied spiral's unrotated racetrack coordinate system. The turn column, which is derived from winding phase, separates successive turns. A complete turn crosses target_x once on each straight branch, producing one notch on either side of the racetrack's z-axis. Partial turns without both crossings are omitted.

Keeping target_x far enough inside a straight racetrack section for the whole tab is the caller's responsibility (see FlatWoundJellyRoll._notch_alignment_target_x). On a straight branch z is constant, so unwrapped distance and x-distance are identical there and no separate endpoint projection is needed.

calculate_variable_thickness_spiral staticmethod
calculate_variable_thickness_spiral(laminate: Laminate, start_radius: float, dtheta: Optional[float] = None, **kwargs) -> ndarray

Integrate a variable-thickness Archimedean-like spiral (clockwise) with adaptive RK4.

Governing relations (parametrized by θ, clockwise so θ decreases from π/2): dr/dθ = t(x) / (2π) ds/dθ = sqrt(r² + (dr/dθ)²) (local arc length rate) dx_unwrapped/dθ = ds/dθ (treat unwrapped length x as accumulated arc length)

With spatially varying thickness t(x) provided by layup.get_thickness_at_x(x).

Improvements over prior implementation
  • 4th order Runge–Kutta integration for coupled (r, x) system.
  • Adaptive step sizing via local Richardson error estimate (1 step h vs 2 half-steps).
  • Automatic fallback to analytic uniform-thickness solution when thickness variation is negligible.
  • Gradient-aware minimum step to avoid under-resolving rapid thickness changes.
  • Early termination and endpoint interpolation to land exactly on total unwrapped length.
  • Conservative iteration / memory limits to avoid runaway loops.
Parameters

dtheta : float, optional Nominal (maximum) angular step magnitude (radians). Smaller steps are chosen adaptively as needed. If None, will look for 'dtheta' in kwargs, defaults to 0.5. **kwargs Additional keyword arguments. Can include 'dtheta' as alternative parameter specification.

Returns

np.ndarray Columns: [theta, x_unwrapped, r, x, z, turns]

Raises

ValueError If laminate total length is invalid or spiral calculation fails

build_component_spirals staticmethod
build_component_spirals(base_spiral: ndarray, layup: Laminate, mandrel_radius: float) -> dict

Build component spirals by mapping flattened center lines onto the wound spiral.

Maps individual electrode and separator components from the layup onto the base spiral geometry, accounting for their specific thickness and positioning.

Parameters

base_spiral : np.ndarray Base spiral coordinates [theta, x_unwrapped, r, x, z, turns] layup : Laminate The layup structure containing component center lines mandrel_radius : float Radius of the mandrel in meters

Returns

dict Component spirals keyed by component name

Raises

ValueError If component mapping fails or invalid geometry is encountered

build_extruded_component_spirals staticmethod
build_extruded_component_spirals(component_spirals: dict, component_thicknesses: dict) -> dict

Build extruded component spirals by radially thickening center line spirals.

For each component, creates a filled shape by: 1. Taking the center line spiral from component_spirals 2. Creating outer spiral by adding thickness/2 to radius 3. Creating inner spiral by subtracting thickness/2 from radius 4. Reversing inner spiral direction for proper winding 5. Combining outer + inner spirals into a closed filled shape

Parameters

component_spirals : dict Component spirals from build_component_spirals component_thicknesses : dict Thicknesses for each component

Returns

dict Extruded component spirals for 2D visualization

Raises

ValueError If extrusion calculation fails or invalid thickness values

calculate_simple_spiral staticmethod
calculate_simple_spiral(n_turns: Optional[float] = None, start_radius: Optional[float] = None, thickness: Optional[float] = None, points_per_turn: int = 100, target_length: Optional[float] = None) -> ndarray

Calculate a simple uniform-thickness Archimedean spiral.

This function generates a basic spiral with constant thickness increment per turn, useful for simple geometric calculations or as a baseline comparison for more complex variable-thickness spirals.

Parameters

n_turns : float, optional Number of turns to generate. Either this or target_length must be provided, but not both. start_radius : float Starting radius in meters thickness : float Constant thickness per turn in meters points_per_turn : int, optional Number of points per complete turn, by default 100 target_length : float, optional Target unwrapped length in meters. Either this or n_turns must be provided, but not both.

Returns

np.ndarray Spiral coordinates with columns: [theta, x_unwrapped, r, x, z, turns] Same format as calculate_variable_thickness_spiral()

Raises

ValueError If both n_turns and target_length are provided, or if neither are provided, or if required parameters are missing.

calculate_variable_thickness_racetrack staticmethod
calculate_variable_thickness_racetrack(laminate: Laminate, mandrel_radius: float, straight_length: float, ds_target: Optional[float] = None, **kwargs) -> ndarray

Calculate spiral path for given mandrel geometry parameters (clockwise direction).

The racetrack consists of: - Two semicircular ends (radius = height/2) - Two straight sections (length = width - height)

Calculates spiral in clockwise direction, consistent with WoundJellyRoll.

Parameters

laminate : Laminate The layup structure containing thickness information mandrel_radius : float Radius of semicircular ends (height/2) in meters straight_length : float Length of straight sections (width - height) in meters ds_target : float, optional Target arc length step size in meters. If None, will look for 'ds_target' in kwargs, defaults to 0.5e-3. **kwargs Additional keyword arguments. Can include 'ds_target' as alternative parameter specification.

Returns

np.ndarray Columns: [theta, x_unwrapped, r, x, z, turns]

calculate_simple_racetrack staticmethod
calculate_simple_racetrack(n_turns: Optional[float] = None, start_radius: Optional[float] = None, straight_length: Optional[float] = None, thickness: Optional[float] = None, points_per_turn: int = 300, target_length: Optional[float] = None) -> ndarray

Calculate a simple uniform-thickness racetrack spiral.

This function generates a basic racetrack spiral with constant thickness increment per turn, useful for simple geometric calculations or as a baseline comparison for more complex variable-thickness racetracks.

Parameters

n_turns : float, optional Number of turns to generate. Either this or target_length must be provided, but not both. start_radius : float Starting radius of semicircular ends in meters straight_length : float Length of straight sections in meters thickness : float Constant thickness per turn in meters points_per_turn : int, optional Number of points per complete turn, by default 100 target_length : float, optional Target unwrapped length in meters. Either this or n_turns must be provided, but not both.

Returns

np.ndarray Racetrack coordinates with columns: [theta, x_unwrapped, r, x, z, turns] Same format as calculate_variable_thickness_racetrack()

Raises

ValueError If both n_turns and target_length are provided, or if neither are provided, or if required parameters are missing.

racetrack_position staticmethod
racetrack_position(theta: float, radius: float, straight_length: float) -> tuple

Calculate x,z position on racetrack at given parametric angle (clockwise direction).

For clockwise motion starting at top-right: - theta=2π: top of right semicircle - theta=3π/2: right side - theta=π: bottom of right semicircle - theta=π/2: bottom of left semicircle - theta=0: top of left semicircle

Parameters

theta : float Parametric angle (0 to 2π), decreases clockwise from 2π radius : float Current layer radius straight_length : float Length of straight sections

Returns

tuple (x_position, z_position) in meters

racetrack_curvature staticmethod
racetrack_curvature(theta: float, radius: float, straight_length: float) -> float

Calculate curvature at given position on racetrack.

Parameters

theta : float Parametric angle (0 to 2π) radius : float Current layer radius straight_length : float Length of straight sections

Returns

float Curvature (1/radius on curves, 0 on straight sections)

build_extruded_component_racetracks staticmethod
build_extruded_component_racetracks(component_spirals: dict, component_thicknesses: dict, mandrel_radius: float, straight_length: float) -> dict

Build extruded component spirals for a given component spirals dictionary and mandrel geometry.

This is a generalized version of build_extruded_component_spirals that can work with any component spirals dictionary and mandrel geometry parameters, used for hot-pressed spirals.

Parameters

component_spirals : dict Component spirals dictionary to extrude component_thicknesses : dict Thicknesses for each component mandrel_radius : float Radius of semicircular ends (height/2) in meters straight_length : float Length of straight sections (width - height) in meters

Returns

dict Extruded component spirals dictionary

create_flat_mandrel_thickness_spirals staticmethod
create_flat_mandrel_thickness_spirals(center_spiral: ndarray, half_thickness: float, mandrel_radius: float, straight_length: float) -> tuple

Create outer and inner spirals by applying thickness in correct directions.

Parameters

center_spiral : np.ndarray Center line spiral coordinates half_thickness : float Half of the component thickness mandrel_radius : float Radius of semicircular ends straight_length : float Length of straight sections

Returns

tuple (outer_spiral, inner_spiral) as numpy arrays

get_coordinate_based_adjustment_direction staticmethod
get_coordinate_based_adjustment_direction(x: float, z: float, mandrel_radius: float, straight_length: float) -> ndarray

Get the direction vector for height adjustment based on actual coordinates.

This method determines the adjustment direction by analyzing the actual position rather than using parametric angles, which avoids bunching issues on curved sections.

Parameters

x : float Current x coordinate z : float Current z coordinate mandrel_radius : float Radius of semicircular ends straight_length : float Length of straight sections

Returns

np.ndarray Unit vector [dx, dz] indicating direction for height adjustment

get_thickness_of_racetrack staticmethod
get_thickness_of_racetrack(coords: ndarray) -> float

Calculate the thickness of a racetrack given its coordinates.

Parameters

coords : np.ndarray Array of shape (N, 2) with columns [x, z]

Returns

float Thickness (z-dimension span) in meters

get_width_of_racetrack staticmethod
get_width_of_racetrack(coords: ndarray) -> float

Calculate the width of a racetrack given its coordinates.

Parameters

coords : np.ndarray Array of shape (N, 2) with columns [x, z]

Returns

float Width (x-dimension span) in meters

format_np_spiral_for_df staticmethod
format_np_spiral_for_df(np_array: ndarray) -> DataFrame

Format numpy spiral array into pandas DataFrame with proper units and column names.

Parameters

np_array : np.ndarray Spiral array with columns [theta, x_unwrapped, r, x, z, turns]

Returns

pd.DataFrame Formatted DataFrame with columns: theta (degrees), x_unwrapped (mm), r (mm), x (mm), z (mm), turns

Raises

ValueError If input array has incorrect shape or invalid data

build_component_racetracks staticmethod
build_component_racetracks(base_spiral, layup, mandrel_radius, straight_length, original_mandrel_radius)

Build component spirals for racetrack geometry based on a base spiral.

This is a generalized version that can work with any mandrel geometry parameters, used for hot-pressed spirals in flat wound jelly rolls.

Parameters

base_spiral : np.ndarray Base spiral to map components onto layup : Laminate Layup object containing component names and flattened center lines mandrel_radius : float Radius of semicircular ends (height/2) in meters (pressed radius) straight_length : float Length of straight sections (width - height) in meters original_mandrel_radius : float Original mandrel radius before pressing in meters

Returns

dict Component spirals dictionary mapping component names to their spiral arrays

apply_flat_mandrel_height_adjustments staticmethod
apply_flat_mandrel_height_adjustments(spiral, height_adjustments, mandrel_radius, straight_length)

Apply height adjustments to spiral points based on their position on the racetrack.

Parameters

spiral : np.ndarray Component spiral array (modified in-place) height_adjustments : np.ndarray Height adjustments to apply at each point mandrel_radius : float Radius of the semicircular ends straight_length : float Length of straight sections

Returns

np.ndarray Modified spiral array

min_thickness_orientation staticmethod
min_thickness_orientation(valid_points: ndarray) -> tuple[float, float, float]

Exact minimal-thickness orientation via convex hull + rotating calipers.

The thickness after rotating by θ equals the extent of the point set along the direction v(θ) = (sin θ, cos θ), and the minimum width of a convex polygon is always attained perpendicular to one of its edges. So the global optimum is found by scanning hull-edge normals — no iterative search, no local minima.

Dense clouds are first reduced to their outer rim (max-radius points per angular bin around the centroid), which preserves the hull vertices of the star-shaped jelly-roll cross-sections to sub-µm accuracy while capping the qhull input size.

Parameters

valid_points : np.ndarray (N, 2) array of finite x-z points.

Returns

tuple[float, float, float] (angle, thickness, width) where angle is the rotation (in radians, mod π) that minimizes the z-extent, thickness is that minimal z-extent, and width is the x-extent at that rotation.

Raises

Exception Degenerate inputs (e.g. all-collinear points) propagate qhull errors; callers fall back to the scalar search.

rotate_spiral_to_minimize_thickness staticmethod
rotate_spiral_to_minimize_thickness(spiral_data, x_col: int = X_COORD_COL, z_col: int = Z_COORD_COL, initial_angle: Optional[float] = None, bracket_width: float = pi / 30.0)

Rotate spiral data in x-z plane to minimize overall thickness.

Finds the rotation angle that minimizes the vertical extent (thickness = max(z) - min(z)) and applies it in-place to the spiral data. The angle is computed exactly with rotating calipers on the convex hull; a Brent scalar search over the raw points remains as a fallback for degenerate geometry that qhull rejects.

Parameters

spiral_data : Union[np.ndarray, Dict[str, np.ndarray]] Either a single spiral array or a dictionary of spiral arrays. Each array should have columns including x and z coordinates. x_col : int, optional Column index for x coordinates (default: X_COORD_COL) z_col : int, optional Column index for z coordinates (default: Z_COORD_COL) initial_angle : float, optional Legacy warm-start seed for the Brent fallback path. The rotating calipers path is non-iterative and ignores it. bracket_width : float, optional Half-width of the warm-start bracket used by the Brent fallback.

Returns

Tuple[Union[np.ndarray, Dict[str, np.ndarray]], float] (rotated_spiral_data, optimal_angle) where optimal_angle is in radians

Notes
  • Rotation is performed about the centroid of all points.
  • Optimization searches over [0, π) since thickness is symmetric about π.
  • Modifies spiral_data in-place and returns it for convenience.
translate_spirals_xz staticmethod
translate_spirals_xz(spiral_data, x_shift: float, z_shift: float, x_col: int = X_COORD_COL, z_col: int = Z_COORD_COL)

Translate spiral coordinates by specified amounts in x and z directions.

This function applies a rigid body translation to spiral geometries. Commonly used for centering spirals or aligning them to a coordinate system.

Parameters

spiral_data : Union[np.ndarray, Dict[str, np.ndarray]] Either a single spiral array or a dictionary of spiral arrays. Each array should have columns including x and z coordinates. x_shift : float Translation distance in x-direction (meters) z_shift : float Translation distance in z-direction (meters) x_col : int, optional Column index for x coordinates (default: X_COORD_COL) z_col : int, optional Column index for z coordinates (default: Z_COORD_COL)

Returns

Union[np.ndarray, Dict[str, np.ndarray]] Translated spiral data (modified in-place and returned)

Notes
  • Modifies spiral_data in-place and returns it for convenience
  • Works with both single arrays and dictionaries of arrays