# Viz

Visualization of solved results: field lines, the field-display models and the render targets a viewer draws.

## femforge.viz

Visualization of solved results: field-line rendering, the field-display models and the composable render targets a viewer draws from a case.vtu.

Modules:

- **`colormaps`** –

  ParaView's colormap presets, registered with matplotlib so every colormap name resolves through pyvista.
- **`field_display`** –

  Field display state for a solved run: which array is colored, how it reduces to a scalar, and the color range that follows; pyvista is imported lazily inside the render half.
- **`field_lines`** –

  3D magnetic field lines from a solved Elmer `case.vtu`: streamlines seeded through the domain and colored by `|B|`.
- **`scene`** –

  Composable render targets: a `Scene` draws several `Renderable`s in one plotter refresh, on the plotter thread.

Classes:

- **`FieldLines`** –

  A `Renderable` for the solved B-field streamlines of one `case.vtu`.
- **`FieldLineOptions`** –

  Typed options for the 3D field-line render.
- **`SeedPlacement`** –

  Where the streamline seed points are placed within the domain bounding box.
- **`Colormap`** –

  Perceptually-uniform matplotlib colormaps for the `|B|` magnitude scale.
- **`OpacityTransfer`** –

  A pyvista opacity transfer function that maps the `|B|` scalar to per-element opacity; the `_r` variants reverse the ramp.
- **`InterpolatorType`** –

  The cell-location strategy the streamline integrator uses (pyvista's `interpolator_type`).
- **`StreamlineActor`** –

  The plotter actor names `draw_streamlines` registers, so toggling the overlay off is a single `remove_actor`.
- **`StreamlineActors`** –

  The precomputed drawables for a field-line view, split from plotting so the expensive part runs off the UI thread.
- **`Association`** –

  Whether a VTU array lives on mesh points or cells (pyvista's `preference`).
- **`BodyPaint`** –

  Where a body's color comes from.
- **`BodyStyle`** –

  A body's own look: where its color comes from, a solid color when it has one, and its opacity.
- **`DisplayMode`** –

  How a field array is reduced to the value that drives the color.
- **`DisplaySpec`** –

  The complete, serializable state of one field display: which array is colored, how it reduces, and how the colors map.
- **`FieldInfo`** –

  One displayable VTU array: where it lives, how wide it is, and which display modes it supports.
- **`FieldScene`** –

  One run's solved bodies as a recolorable field display.
- **`SceneActor`** –

  The plotter actor names a `FieldScene` registers, so a toggle removes exactly one.
- **`Renderable`** –

  Something a workbench run returns for display: it draws itself into `plotter` (on the plotter thread).
- **`Scene`** –

  An ordered group of `Renderable`s drawn into one plotter refresh: clear once, draw in order (later items on top), repaint once.

Functions:

- **`select_flux_density_array`** –

  Pick the B array from a VTU's array names: the nodal form first (no cell→point projection needed), then the elemental `"... e"`, then any name containing the phrase (case-insensitive).
- **`seed_points`** –

  The `(N, 3)` streamline seed coordinates for the domain `bounds` and `options` (pure Python — unit-testable without a render context).
- **`build_streamlines`** –

  Seed and integrate |B| streamlines through a dataset that already carries B as **point** data, without touching a plotter.
- **`build_streamlines_from_vtu`** –

  Read a solved `case.vtu` and integrate its B-field streamlines into a drawable `StreamlineActors` — no plotter touched, safe off the UI thread.
- **`prepare_streamline_source`** –

  Narrow a read grid to the integration region and put B on its points, returning the dataset and the B array name — the read-independent half of `build_streamlines_from_vtu`.
- **`draw_streamlines`** –

  Draw a precomputed `StreamlineActors` into `plotter` without clearing or showing it, so it composes inside a `Scene`.
- **`render_streamlines`** –

  One-shot: build streamlines through `dataset` and draw them, in a plotter of its own when `plotter` is None.
- **`render_field_lines`** –

  Render the magnetic flux-density field lines from a solved `case.vtu`, headless to a PNG or in an interactive window.
- **`field_infos`** –

  Every displayable array of a VTU as a `FieldInfo`, `GeometryIds` excluded, sorted by display name.
- **`reduce_field`** –

  The scalar a display mode draws from one field array.
- **`polar_angle`** –

  Each vector's XY direction as a cyclic coordinate in `[0, 1]` — `atan2(y, x)` mapped off `[-pi, pi]`.
- **`polar_rgba`** –

  Per-vector RGBA for `DisplayMode.POLAR_XY`: hue from the XY angle through a cyclic `cmap`, brightness and alpha from the sigmoid-scaled XY magnitude.
- **`color_range`** –

  The `(lo, hi)` color limits spanning every scalar chunk's own range, from the finite values only; an empty selection gives `(0.0, 1.0)`.
- **`camera_needs_reset`** –

  Whether the camera has lost the model, with the box behind it or outside its view cone; a camera inside the box always keeps its view.
- **`plotly_colorscale`** –

  A plotly colorscale from sampled RGB(A) rows: evenly-spaced stops paired with `"rgb(r,g,b)"` strings.
- **`log_colorbar_ticks`** –

  Decade tick positions spanning a log color range, with their `1e`-notation labels.
- **`mesh3d_traces`** –

  One plotly `Mesh3d` trace as a plain JSON dict: a body's triangulated surface colored by `intensity`, or by `color` alone.
- **`outline_traces`** –

  One plotly `Scatter3d` lines trace drawing every `(N, 2, 3)` segment, NaN-separated so the segments stay disjoint.
- **`list_fields`** –

  Every displayable field of a VTU grid (`GeometryIds` excluded), sorted by display name.
- **`split_by_body`** –

  The grid split into one block per named body, keyed by its `entities.json` name via the `GeometryIds` cell array.
- **`feature_outline`** –

  The feature and boundary edges of a multiblock's outer surface — the black wireframe drawn over the field, the body outlines on a 2D slice — or None when the surface has none.
- **`model_dim_of`** –

  2 for a flat (axisymmetric slice) grid with no Z extent, else 3.
- **`sample_colormap`** –

  `count` evenly-spaced RGBA rows from a named colormap, resolved through pyvista's `get_cmap_safe`.
- **`surface_triangles`** –

  A block's outer surface as `(points, triangles)`: an `(N, 3)` coordinate array and an `(M, 3)` vertex-index array.
- **`outline_segments`** –

  The feature edges as an `(N, 2, 3)` array of line segments.

Attributes:

- **`Bounds`** –

  A pyvista axis-aligned bounding box, as `(xmin, xmax, ymin, ymax, zmin, zmax)`.
- **`Point3`** –

  One 3D seed coordinate, as `(x, y, z)`.
- **`FLUX_DENSITY`** –

  The MagnetoDynamicsCalcFields name for B in its nodal form; the elemental form the solver writes by default appends `" e"`.
- **`FIELD_RENAMES`** (`dict[str, str]`) –

  Display-name overrides for solver array names that title-casing alone leaves cryptic.
- **`SCALARS_ARRAY`** –

  The point/cell array every reduction is written to, so one mapper name serves every field and mode.

### Bounds

```python
Bounds = tuple[float, float, float, float, float, float]
```

A pyvista axis-aligned bounding box, as `(xmin, xmax, ymin, ymax, zmin, zmax)`.

### Point3

```python
Point3 = tuple[float, float, float]
```

One 3D seed coordinate, as `(x, y, z)`.

### FLUX\_DENSITY

```python
FLUX_DENSITY = 'magnetic flux density'
```

The MagnetoDynamicsCalcFields name for B in its nodal form; the elemental form the solver writes by default appends `" e"`.

### FIELD\_RENAMES

```python
FIELD_RENAMES: dict[str, str] = {'Jfix': 'Fixed Current Density'}
```

Display-name overrides for solver array names that title-casing alone leaves cryptic.

### SCALARS\_ARRAY

```python
SCALARS_ARRAY = 'display_scalars'
```

The point/cell array every reduction is written to, so one mapper name serves every field and mode.

### FieldLines

```python
FieldLines(vtu_path: str | Path, options: FieldLineOptions | None = None)
```

A `Renderable` for the solved B-field streamlines of one `case.vtu`.

VTU read + streamline integration run in `__post_init__` (whichever worker thread constructs it); `render` only draws precomputed actors, keeping the plotter thread free.

Methods:

- **`render`** –

  Draw the precomputed field lines into `plotter`.

Attributes:

- **`vtu_path`** (`str | Path`) –

  The solved `case.vtu` whose B field the lines follow.
- **`options`** (`FieldLineOptions | None`) –

  The streamline options; None uses the defaults.

#### vtu\_path

```python
vtu_path: str | Path
```

The solved `case.vtu` whose B field the lines follow.

#### options

```python
options: FieldLineOptions | None = None
```

The streamline options; None uses the defaults.

#### render

```python
render(plotter: Plotter) -> None
```

Draw the precomputed field lines into `plotter`.

### FieldLineOptions

Bases: `BaseModel`

Typed options for the 3D field-line render.

Config:

- `use_attribute_docstrings`: `True`
- `validate_assignment`: `True`

Fields:

- `seed_placement` (`SeedPlacement`)
- `seed_grid` (`bool`)
- `line_count` (`int`)
- `max_length` (`float`)
- `colormap` (`Colormap | str | list[str]`)
- `invert` (`bool`)
- `log_scale` (`bool`)
- `clim` (`tuple[float, float] | None`)
- `opacity_map` (`tuple[float, float] | None`)
- `opacity` (`float | OpacityTransfer`)
- `interpolator` (`InterpolatorType`)
- `seed_fraction` (`float`)
- `region_array` (`str | None`)
- `region_id` (`int | None`)
- `clip_to_region` (`bool`)
- `tube_radius` (`float | None`)
- `initial_step_length` (`float`)
- `min_step_length` (`float`)
- `max_step_length` (`float`)
- `show_outline` (`bool`)
- `vectors` (`str | None`)

#### seed\_placement

```python
seed_placement: SeedPlacement = SeedPlacement.VOLUME
```

Where to seed the streamlines within the domain bounding box.

#### seed\_grid

```python
seed_grid: bool = True
```

Seed the streamlines on a grid (`True`) or at random points (`False`).

#### line\_count

```python
line_count: int = 12
```

Seed points per axis: `CENTER_PLANE` seeds `line_count²`, `VOLUME` `line_count³`, `GAP_LINE` `line_count`.

#### max\_length

```python
max_length: float = 0.75
```

`[-]`: Fraction of the domain diagonal a streamline may integrate along, keeping the picture framed on the magnets.

#### colormap

```python
colormap: Colormap | str | list[str] = Colormap.VIRIDIS
```

The `|B|` colormap: a `Colormap` member, any matplotlib or colorcet colormap name, or a list of colors interpolated across the scalar range.

#### invert

```python
invert: bool = False
```

Reverse the colormap.

#### log\_scale

```python
log_scale: bool = False
```

Map `|B|` to colors logarithmically.

#### clim

```python
clim: tuple[float, float] | None = None
```

`[?]`: The `|B|` color limits, or None for the lines' own range.

#### opacity\_map

```python
opacity_map: tuple[float, float] | None = None
```

`[-]`: The opacity at the low and high color limits, linear in the color mapping between them; it replaces an `OpacityTransfer`.

#### opacity

```python
opacity: float | OpacityTransfer = 1.0
```

`[-]`: Line or tube opacity: a constant in `[0, 1]`, or an `OpacityTransfer` that maps `|B|` to opacity like the color.

#### interpolator

```python
interpolator: InterpolatorType = InterpolatorType.CELL
```

The integrator's cell-location strategy. `CELL` (default) fixes the straight "spike" streamlines a point locator produces near the air↔magnet interface.

#### seed\_fraction

```python
seed_fraction: float = 0.6
```

`[-]`: Fraction of the bounding box, centered, that the seed grid spans, keeping seeds out of the outer air shell where B ≈ 0.

#### region\_array

```python
region_array: str | None = None
```

Name of a cell-data region-id array (e.g. Elmer's `"GeometryIds"`) identifying each cell's body. Used with `region_id` and `clip_to_region`.

#### region\_id

```python
region_id: int | None = None
```

The single region id to confine integration to when `clip_to_region` is set (e.g. the air body id).

#### clip\_to\_region

```python
clip_to_region: bool = False
```

Confine streamline integration to `region_id` (needs `region_array` and `region_id`), which keeps elemental B from averaging across the air↔magnet interface.

#### tube\_radius

```python
tube_radius: float | None = None
```

`r_tube` `[m]`: Streamline tube radius; None draws the streamlines as plain poly-lines.

#### initial\_step\_length

```python
initial_step_length: float = 0.5
```

`[-]`: The integrator's initial step, in cell lengths; smaller values follow tight curves.

#### min\_step\_length

```python
min_step_length: float = 0.01
```

`[-]`: The integrator's smallest step, in cell lengths.

#### max\_step\_length

```python
max_step_length: float = 1.0
```

`[-]`: The integrator's largest step, in cell lengths.

#### show\_outline

```python
show_outline: bool = True
```

Draw the domain bounding-box outline for spatial context.

#### vectors

```python
vectors: str | None = None
```

The 3-component array the lines follow; None picks the magnetic flux density.

### SeedPlacement

Bases: `StrEnum`

Where the streamline seed points are placed within the domain bounding box.

Attributes:

- **`CENTER_PLANE`** –

  A `line_count` × `line_count` grid on the mid-Z plane (the field-line cross-section).
- **`VOLUME`** –

  A `line_count`³ grid filling the box — denser, fuller 3D coverage (the default).
- **`GAP_LINE`** –

  A single line of `line_count` points along X through the center (e.g. across the Halbach gap).

#### CENTER\_PLANE

```python
CENTER_PLANE = 'center_plane'
```

A `line_count` × `line_count` grid on the mid-Z plane (the field-line cross-section).

#### VOLUME

```python
VOLUME = 'volume'
```

A `line_count`³ grid filling the box — denser, fuller 3D coverage (the default).

#### GAP\_LINE

```python
GAP_LINE = 'gap_line'
```

A single line of `line_count` points along X through the center (e.g. across the Halbach gap).

### Colormap

Bases: `StrEnum`

Perceptually-uniform matplotlib colormaps for the `|B|` magnitude scale.

Attributes:

- **`VIRIDIS`** –

  Dark blue through green to yellow.
- **`PLASMA`** –

  Dark blue through magenta to yellow.
- **`MAGMA`** –

  Black through red to pale yellow.
- **`TURBO`** –

  A smooth rainbow from dark blue to dark red.
- **`COOLWARM`** –

  A diverging map from blue through grey to red.

#### VIRIDIS

```python
VIRIDIS = 'viridis'
```

Dark blue through green to yellow.

#### PLASMA

```python
PLASMA = 'plasma'
```

Dark blue through magenta to yellow.

#### MAGMA

```python
MAGMA = 'magma'
```

Black through red to pale yellow.

#### TURBO

```python
TURBO = 'turbo'
```

A smooth rainbow from dark blue to dark red.

#### COOLWARM

```python
COOLWARM = 'coolwarm'
```

A diverging map from blue through grey to red.

### OpacityTransfer

Bases: `StrEnum`

A pyvista opacity transfer function that maps the `|B|` scalar to per-element opacity; the `_r` variants reverse the ramp.

Attributes:

- **`LINEAR`** –

  Opacity rises linearly with `|B|`.
- **`LINEAR_R`** –

  Opacity falls linearly with `|B|`.
- **`GEOM`** –

  Opacity rises geometrically with `|B|`.
- **`GEOM_R`** –

  Opacity falls geometrically with `|B|`.
- **`SIGMOID`** –

  Opacity follows a sigmoid in `|B|`, so weak-field lines fade out.
- **`SIGMOID_R`** –

  Opacity follows a reversed sigmoid in `|B|`, so strong-field lines fade out.

#### LINEAR

```python
LINEAR = 'linear'
```

Opacity rises linearly with `|B|`.

#### LINEAR\_R

```python
LINEAR_R = 'linear_r'
```

Opacity falls linearly with `|B|`.

#### GEOM

```python
GEOM = 'geom'
```

Opacity rises geometrically with `|B|`.

#### GEOM\_R

```python
GEOM_R = 'geom_r'
```

Opacity falls geometrically with `|B|`.

#### SIGMOID

```python
SIGMOID = 'sigmoid'
```

Opacity follows a sigmoid in `|B|`, so weak-field lines fade out.

#### SIGMOID\_R

```python
SIGMOID_R = 'sigmoid_r'
```

Opacity follows a reversed sigmoid in `|B|`, so strong-field lines fade out.

### InterpolatorType

Bases: `StrEnum`

The cell-location strategy the streamline integrator uses (pyvista's `interpolator_type`).

Both integrate the same point vector field; this only changes how each step finds its containing cell.

Attributes:

- **`CELL`** –

  Locate cells with a cell locator, the right default for this geometry.
- **`POINT`** –

  Locate cells via the nearest mesh point (pyvista's default). Faster, but near interfaces/thin features it can grab the wrong

#### CELL

```python
CELL = 'cell'
```

Locate cells with a cell locator, the right default for this geometry.

It stays robust near the air↔magnet interface and thin air gaps, where a point locator mislocates a step into the wrong (magnet) cell and sends the streamline off as a straight "spike".

#### POINT

```python
POINT = 'point'
```

Locate cells via the nearest mesh point (pyvista's default). Faster, but near interfaces/thin features it can grab the wrong cell's vector and produce the spike streamlines.

### StreamlineActor

Bases: `StrEnum`

The plotter actor names `draw_streamlines` registers, so toggling the overlay off is a single `remove_actor`.

Attributes:

- **`LINES`** –

  The integrated streamlines.
- **`OUTLINE`** –

  The domain's bounding-box outline.

#### LINES

```python
LINES = 'streamlines'
```

The integrated streamlines.

#### OUTLINE

```python
OUTLINE = 'streamlines-outline'
```

The domain's bounding-box outline.

### StreamlineActors

```python
StreamlineActors(streamlines: PolyData, b_name: str, outline: PolyData | None, magnitudes: list[NDArray[float64]])
```

The precomputed drawables for a field-line view, split from plotting so the expensive part runs off the UI thread.

Built by `build_streamlines` (worker-thread safe) and drawn by `draw_streamlines` (plotter thread only).

Attributes:

- **`streamlines`** (`PolyData`) –

  The integrated streamlines, clipped to the integration region.
- **`b_name`** (`str`) –

  The name of the B array the lines carry for coloring.
- **`outline`** (`PolyData | None`) –

  The domain's bounding-box outline; None when `show_outline` is off.
- **`magnitudes`** (`list[NDArray[float64]]`) –

  The integrated field's magnitudes over the integration region, one chunk per body, which set the lines' color range.

#### streamlines

```python
streamlines: PolyData
```

The integrated streamlines, clipped to the integration region.

#### b\_name

```python
b_name: str
```

The name of the B array the lines carry for coloring.

#### outline

```python
outline: PolyData | None
```

The domain's bounding-box outline; None when `show_outline` is off.

#### magnitudes

```python
magnitudes: list[NDArray[float64]]
```

The integrated field's magnitudes over the integration region, one chunk per body, which set the lines' color range.

### Association

Bases: `StrEnum`

Whether a VTU array lives on mesh points or cells (pyvista's `preference`).

Attributes:

- **`POINT`** –

  One value per mesh point.
- **`CELL`** –

  One value per mesh cell.
- **`intensitymode`** (`str`) –

  The plotly `Mesh3d.intensitymode` name for this association.

#### POINT

```python
POINT = 'point'
```

One value per mesh point.

#### CELL

```python
CELL = 'cell'
```

One value per mesh cell.

#### intensitymode

```python
intensitymode: str
```

The plotly `Mesh3d.intensitymode` name for this association.

### BodyPaint

Bases: `StrEnum`

Where a body's color comes from.

Attributes:

- **`FIELD`** –

  The field colormap, by the displayed value.
- **`COLORMAP`** –

  One color of the body colormap, spread evenly over every body that takes it.
- **`SOLID`** –

  The body's own `color`.

#### FIELD

```python
FIELD = 'field'
```

The field colormap, by the displayed value.

#### COLORMAP

```python
COLORMAP = 'colormap'
```

One color of the body colormap, spread evenly over every body that takes it.

#### SOLID

```python
SOLID = 'solid'
```

The body's own `color`.

### BodyStyle

Bases: `BaseModel`

A body's own look: where its color comes from, a solid color when it has one, and its opacity.

Config:

- `use_attribute_docstrings`: `True`
- `frozen`: `True`

Fields:

- `body` (`str`)
- `paint` (`BodyPaint`)
- `color` (`str | None`)
- `opacity` (`float`)

Validators:

- `_solid_has_color`

#### body

```python
body: str
```

The body's `entities.json` name.

#### paint

```python
paint: BodyPaint = BodyPaint.FIELD
```

Where the body's color comes from.

#### color

```python
color: str | None = None
```

Any color pyvista resolves (a name or a hex triplet); a SOLID body needs one, the others ignore it.

#### opacity

```python
opacity: float = 1.0
```

`[-]`: The body's own opacity, multiplied by the display's.

### DisplayMode

Bases: `StrEnum`

How a field array is reduced to the value that drives the color.

Attributes:

- **`VALUE`** –

  The value itself.
- **`ABSVALUE`** –

  The absolute value.
- **`X`** –

  The x component.
- **`Y`** –

  The y component.
- **`Z`** –

  The z component.
- **`MAGNITUDE`** –

  The vector magnitude.
- **`POLAR_XY`** –

  The in-plane direction as hue, brightened by the in-plane magnitude.
- **`label`** (`str`) –

  The mode's name as it reads in a picker.

#### VALUE

```python
VALUE = 'value'
```

The value itself.

#### ABSVALUE

```python
ABSVALUE = 'absolute_value'
```

The absolute value.

#### X

```python
X = 'x'
```

The x component.

#### Y

```python
Y = 'y'
```

The y component.

#### Z

```python
Z = 'z'
```

The z component.

#### MAGNITUDE

```python
MAGNITUDE = 'magnitude'
```

The vector magnitude.

#### POLAR\_XY

```python
POLAR_XY = 'polar_xy'
```

The in-plane direction as hue, brightened by the in-plane magnitude.

#### label

```python
label: str
```

The mode's name as it reads in a picker.

### DisplaySpec

Bases: `BaseModel`

The complete, serializable state of one field display: which array is colored, how it reduces, and how the colors map.

Config:

- `use_attribute_docstrings`: `True`
- `validate_assignment`: `True`

Fields:

- `field` (`str`)
- `mode` (`DisplayMode`)
- `colormap` (`str`)
- `log_scale` (`bool`)
- `invert` (`bool`)
- `clim` (`tuple[float, float] | None`)
- `robust` (`bool`)
- `hidden_bodies` (`tuple[str, ...]`)
- `outline` (`bool`)
- `opacity` (`float`)
- `opacity_map` (`tuple[float, float] | None`)
- `body_styles` (`tuple[BodyStyle, ...]`)
- `body_colormap` (`str`)
- `streamlines` (`FieldLineOptions | None`)

#### field

```python
field: str = ''
```

Name of the VTU array to color by; empty means "whichever the run offers first".

#### mode

```python
mode: DisplayMode = DisplayMode.VALUE
```

How the array is reduced to the displayed value.

#### colormap

```python
colormap: str = 'viridis'
```

Colormap name, resolved through pyvista's `get_cmap_safe` (matplotlib and colorcet names both work).

#### log\_scale

```python
log_scale: bool = False
```

Map the color range logarithmically; only the strictly positive values take part.

#### invert

```python
invert: bool = False
```

Reverse the colormap.

#### clim

```python
clim: tuple[float, float] | None = None
```

`[?]`: Pinned color limits in the displayed value's unit, or None to recompute them from the data on every apply.

#### robust

```python
robust: bool = True
```

Clamp the automatic limits to the 2-98 percentiles so a few outlier cells cannot flatten the picture.

#### hidden\_bodies

```python
hidden_bodies: tuple[str, ...] = ()
```

Body names to hide, by their `entities.json` name.

#### outline

```python
outline: bool = True
```

Draw the black feature-edge wireframe over the visible bodies.

#### opacity

```python
opacity: float = 1.0
```

`[-]`: Multiplies every body's own opacity.

#### opacity\_map

```python
opacity_map: tuple[float, float] | None = None
```

`[-]`: The opacity at the low and high color limits, linear in the color mapping between them; None draws `opacity` throughout.

#### body\_styles

```python
body_styles: tuple[BodyStyle, ...] = ()
```

Bodies with a color source or an opacity of their own; the rest take the field colormap at full opacity.

#### body\_colormap

```python
body_colormap: str = 'batlow'
```

The colormap the COLORMAP bodies share, in `body_styles` order.

#### streamlines

```python
streamlines: FieldLineOptions | None = None
```

Options for the field-line overlay, or None to draw none.

#### solid

```python
solid() -> dict[str, str]
```

The color of every body not on the field colormap, by body: its own, or its share of the body colormap.

#### body\_opacity

```python
body_opacity(body: str) -> float
```

A body's drawn opacity: its own times the overall one.

#### unmatched

```python
unmatched(bodies: Iterable[str]) -> list[str]
```

The `hidden_bodies` and `body_styles` names missing from `bodies`, in spec order.

#### fitted

```python
fitted(fields: Sequence[FieldInfo]) -> DisplaySpec
```

This spec snapped to what a run actually offers: an unset field takes the first available one and a mode the field disallows falls back to its first allowed mode.

An unknown field name raises `KeyError` naming the available arrays.

### FieldInfo

```python
FieldInfo(name: str, association: Association, components: int, dtype: str)
```

One displayable VTU array: where it lives, how wide it is, and which display modes it supports.

Attributes:

- **`name`** (`str`) –

  The array's name in the VTU.
- **`association`** (`Association`) –

  Whether the array lives on points or cells.
- **`components`** (`int`) –

  The number of components per value: 1 for a scalar, 3 for a vector.
- **`dtype`** (`str`) –

  The array's numpy dtype name.
- **`display_name`** (`str`) –

  The array's name as it reads in a picker, with the `FIELD_RENAMES` overrides applied.
- **`modes`** (`tuple[DisplayMode, ...]`) –

  The display modes this array supports, the default first: the vector set for a 3-component field, the scalar pair otherwise.

#### name

```python
name: str
```

The array's name in the VTU.

#### association

```python
association: Association
```

Whether the array lives on points or cells.

#### components

```python
components: int
```

The number of components per value: 1 for a scalar, 3 for a vector.

#### dtype

```python
dtype: str
```

The array's numpy dtype name.

#### display\_name

```python
display_name: str
```

The array's name as it reads in a picker, with the `FIELD_RENAMES` overrides applied.

#### modes

```python
modes: tuple[DisplayMode, ...]
```

The display modes this array supports, the default first: the vector set for a 3-component field, the scalar pair otherwise.

### FieldScene

```python
FieldScene(*, grid: UnstructuredGrid, blocks: MultiBlock, fields: Sequence[FieldInfo], model_dim: int, run_id: str, bodies: Bodies | None = None)
```

One run's solved bodies as a recolorable field display.

It is a `Renderable` for the pyvista path and the trace source for the 2D plotly path; both read the same `DisplaySpec`.

Parameters:

- **`grid`** (`UnstructuredGrid`) –

  The solved VTU grid.
- **`blocks`** (`MultiBlock`) –

  The grid split into one block per named body.
- **`fields`** (`Sequence[FieldInfo]`) –

  The displayable arrays.
- **`model_dim`** (`int`) –

  2 for an axisymmetric slice, else 3.
- **`run_id`** (`str`) –

  The run bundle's name.
- **`bodies`** (`Bodies | None`, default: `None` ) –

  The named bodies from `entities.json`; None names none.

Methods:

- **`from_run`** –

  Open a run bundle's VTU as a field scene, its bodies split and named from `entities.json`.
- **`field_info`** –

  The `FieldInfo` for an array name, resolved the same way a field query resolves one.
- **`apply`** –

  Write the spec's reduction into every block's `display_scalars` array and return the color limits it produced.
- **`render`** –

  Draw the whole display into `plotter`: the composite body actor, then everything `update` owns; solid bodies an earlier scene left go first.
- **`update`** –

  Recolor an already-rendered scene in place — the cheap path a panel calls on every knob change; field lines are re-integrated only when their options changed.
- **`shown_blocks`** –

  The blocks of every body not in `hidden`, under their names.
- **`integrated`** –

  The cached field lines when `options` only restyle them, None when they need integrating.
- **`integrate_streamlines`** –

  Seed and integrate the field lines through this scene's grid — the compute half, safe off the UI thread.
- **`region_magnitudes`** –

  The magnitudes of array `name` per body of a region (every body for None), the chunks a field-line range spans.
- **`show_streamlines`** –

  Draw integrated field lines into `plotter` in the display's mapping, record their options as the drawn ones and keep the lines for a restyle.
- **`lines_pinned`** –

  Whether field lines following `vectors` take the display's pinned range: it is pinned and shows that field's magnitude.
- **`lines_title`** –

  The field lines' scalar bar title: the integrated field's name.
- **`line_style`** –

  The field lines in the display's colormap, inversion, log scale and opacity map; a polar display leaves them as asked.
- **`figure`** –

  The plotly figure dict for the 2D path: one `Mesh3d` per body (hidden ones `legendonly`) plus the outline, under an orthographic top-down camera.

Attributes:

- **`drawn_lines`** (`FieldLineOptions | None`) –

  The styled options of the lines the plotter holds.

#### drawn\_lines

```python
drawn_lines: FieldLineOptions | None = None
```

The styled options of the lines the plotter holds.

#### from\_run

```python
from_run(run: Run) -> FieldScene
```

Open a run bundle's VTU as a field scene, its bodies split and named from `entities.json`.

#### field\_info

```python
field_info(name: str) -> FieldInfo
```

The `FieldInfo` for an array name, resolved the same way a field query resolves one.

#### apply

```python
apply(spec: DisplaySpec) -> tuple[float, float]
```

Write the spec's reduction into every block's `display_scalars` array and return the color limits it produced.

#### render

```python
render(plotter: Plotter) -> None
```

Draw the whole display into `plotter`: the composite body actor, then everything `update` owns; solid bodies an earlier scene left go first.

#### update

```python
update(plotter: Plotter, spec: DisplaySpec) -> None
```

Recolor an already-rendered scene in place — the cheap path a panel calls on every knob change; field lines are re-integrated only when their options changed.

#### shown\_blocks

```python
shown_blocks(hidden: Iterable[str]) -> MultiBlock
```

The blocks of every body not in `hidden`, under their names.

#### integrated

```python
integrated(options: FieldLineOptions) -> StreamlineActors | None
```

The cached field lines when `options` only restyle them, None when they need integrating.

#### integrate\_streamlines

```python
integrate_streamlines(options: FieldLineOptions) -> StreamlineActors
```

Seed and integrate the field lines through this scene's grid — the compute half, safe off the UI thread.

#### region\_magnitudes

```python
region_magnitudes(name: str, region_id: int | None) -> list[FloatArray]
```

The magnitudes of array `name` per body of a region (every body for None), the chunks a field-line range spans.

#### show\_streamlines

```python
show_streamlines(plotter: Plotter, actors: StreamlineActors, options: FieldLineOptions) -> None
```

Draw integrated field lines into `plotter` in the display's mapping, record their options as the drawn ones and keep the lines for a restyle.

Parameters:

- **`plotter`** (`Plotter`) –

  The plotter to draw into.
- **`actors`** (`StreamlineActors`) –

  The integrated field lines.
- **`options`** (`FieldLineOptions`) –

  The options the lines were integrated with.

#### lines\_pinned

```python
lines_pinned(vectors: str) -> bool
```

Whether field lines following `vectors` take the display's pinned range: it is pinned and shows that field's magnitude.

#### lines\_title

```python
lines_title(b_name: str) -> str
```

The field lines' scalar bar title: the integrated field's name.

#### line\_style

```python
line_style(options: FieldLineOptions, actors: StreamlineActors) -> FieldLineOptions
```

The field lines in the display's colormap, inversion, log scale and opacity map; a polar display leaves them as asked.

Their range is the display's pinned one while it shows their field's magnitude, else the automatic range of that magnitude over their own region, whatever the bodies show.

#### figure

```python
figure(spec: DisplaySpec) -> dict[str, object]
```

The plotly figure dict for the 2D path: one `Mesh3d` per body (hidden ones `legendonly`) plus the outline, under an orthographic top-down camera.

`POLAR_XY` falls back to the magnitude, a log scale plots `log10` with decade ticks, and `opacity_map` draws as `opacity`.

### SceneActor

Bases: `StrEnum`

The plotter actor names a `FieldScene` registers, so a toggle removes exactly one.

Methods:

- **`of`** –

  The actor name of one body under this prefix.

Attributes:

- **`BODIES`** –

  The colored field over the shown bodies.
- **`OUTLINE`** –

  The feature-edge outline of the bodies.
- **`STYLED`** –

  The prefix of a styled body's own actor, followed by the body name.

#### BODIES

```python
BODIES = 'field-bodies'
```

The colored field over the shown bodies.

#### OUTLINE

```python
OUTLINE = 'field-outline'
```

The feature-edge outline of the bodies.

#### STYLED

```python
STYLED = 'field-styled'
```

The prefix of a styled body's own actor, followed by the body name.

#### of

```python
of(body: str) -> str
```

The actor name of one body under this prefix.

### Renderable

Bases: `Protocol`

Something a workbench run returns for display: it draws itself into `plotter` (on the plotter thread).

Methods:

- **`render`** –

  Draw into `plotter`, on the plotter thread.

#### render

```python
render(plotter: Plotter) -> None
```

Draw into `plotter`, on the plotter thread.

### Scene

```python
Scene(*items: Renderable)
```

An ordered group of `Renderable`s drawn into one plotter refresh: clear once, draw in order (later items on top), repaint once.

Scene is itself a `Renderable`, so scenes can nest.

Methods:

- **`add`** –

  Append a renderable to draw; returns `self` so calls chain.
- **`render`** –

  Clear the plotter, draw every item in order, and repaint — once. Call on the plotter thread.

#### add

```python
add(item: Renderable) -> Scene
```

Append a renderable to draw; returns `self` so calls chain.

#### render

```python
render(plotter: Plotter) -> None
```

Clear the plotter, draw every item in order, and repaint — once. Call on the plotter thread.

### select\_flux\_density\_array

```python
select_flux_density_array(names: Iterable[str]) -> str
```

Pick the B array from a VTU's array names: the nodal form first (no cell→point projection needed), then the elemental `"... e"`, then any name containing the phrase (case-insensitive).

Raises `KeyError` naming the available arrays if none matches.

### seed\_points

```python
seed_points(bounds: Bounds, options: FieldLineOptions) -> list[Point3]
```

The `(N, 3)` streamline seed coordinates for the domain `bounds` and `options` (pure Python — unit-testable without a render context).

The grid is centered and spans `seed_fraction` of each axis, keeping seeds out of the outer air shell where B ≈ 0.

### build\_streamlines

```python
build_streamlines(dataset: DataSet, b_name: str, options: FieldLineOptions | None = None) -> StreamlineActors
```

Seed and integrate |B| streamlines through a dataset that already carries B as **point** data, without touching a plotter.

Parameters:

- **`dataset`** (`DataSet`) –

  The dataset carrying B on its points.
- **`b_name`** (`str`) –

  The point array the lines follow.
- **`options`** (`FieldLineOptions | None`, default: `None` ) –

  The field-line options; None takes the defaults.

### build\_streamlines\_from\_vtu

```python
build_streamlines_from_vtu(vtu_path: str | Path, options: FieldLineOptions | None = None) -> StreamlineActors
```

Read a solved `case.vtu` and integrate its B-field streamlines into a drawable `StreamlineActors` — no plotter touched, safe off the UI thread.

### prepare\_streamline\_source

```python
prepare_streamline_source(grid: UnstructuredGrid, options: FieldLineOptions) -> tuple[DataSet, str]
```

Narrow a read grid to the integration region and put B on its points, returning the dataset and the B array name — the read-independent half of `build_streamlines_from_vtu`.

### draw\_streamlines

```python
draw_streamlines(actors: StreamlineActors, options: FieldLineOptions, plotter: Plotter) -> None
```

Draw a precomputed `StreamlineActors` into `plotter` without clearing or showing it, so it composes inside a `Scene`.

Parameters:

- **`actors`** (`StreamlineActors`) –

  The integrated lines; an empty set removes any lines drawn before.
- **`options`** (`FieldLineOptions`) –

  The field-line options that style them.
- **`plotter`** (`Plotter`) –

  The plotter to draw into, on its own thread.

### render\_streamlines

```python
render_streamlines(dataset: DataSet, b_name: str, options: FieldLineOptions | None = None, *, off_screen: bool = False, screenshot: str | Path | None = None, plotter: Plotter | None = None) -> None
```

One-shot: build streamlines through `dataset` and draw them, in a plotter of its own when `plotter` is None.

Parameters:

- **`dataset`** (`DataSet`) –

  The dataset carrying B on its points.
- **`b_name`** (`str`) –

  The point array the lines follow.
- **`options`** (`FieldLineOptions | None`, default: `None` ) –

  The field-line options; None takes the defaults.
- **`off_screen`** (`bool`, default: `False` ) –

  Render without a window.
- **`screenshot`** (`str | Path | None`, default: `None` ) –

  The PNG to write when rendering off screen.
- **`plotter`** (`Plotter | None`, default: `None` ) –

  A live plotter to add the lines to instead of opening one.

### render\_field\_lines

```python
render_field_lines(vtu_path: str | Path, options: FieldLineOptions | None = None, *, off_screen: bool = False, screenshot: str | Path | None = None, plotter: Plotter | None = None) -> None
```

Render the magnetic flux-density field lines from a solved `case.vtu`, headless to a PNG or in an interactive window.

Parameters:

- **`vtu_path`** (`str | Path`) –

  The solved `case.vtu`.
- **`options`** (`FieldLineOptions | None`, default: `None` ) –

  The field-line options; None takes the defaults.
- **`off_screen`** (`bool`, default: `False` ) –

  Render without a window.
- **`screenshot`** (`str | Path | None`, default: `None` ) –

  The PNG to write when rendering off screen.
- **`plotter`** (`Plotter | None`, default: `None` ) –

  A live plotter to add the lines to instead of opening one.

### field\_infos

```python
field_infos(point_arrays: Mapping[str, AnyArray], cell_arrays: Mapping[str, AnyArray]) -> list[FieldInfo]
```

Every displayable array of a VTU as a `FieldInfo`, `GeometryIds` excluded, sorted by display name.

### reduce\_field

```python
reduce_field(values: AnyArray, mode: DisplayMode) -> FloatArray
```

The scalar a display mode draws from one field array.

`VALUE` passes the array through and `POLAR_XY` returns the XY magnitude (the quantity that sets the color range; the angle rides along as hue).

### polar\_angle

```python
polar_angle(values: AnyArray) -> FloatArray
```

Each vector's XY direction as a cyclic coordinate in `[0, 1]` — `atan2(y, x)` mapped off `[-pi, pi]`.

### polar\_rgba

```python
polar_rgba(values: AnyArray, lo: float, hi: float, cmap: Colormapper) -> Float32Array
```

Per-vector RGBA for `DisplayMode.POLAR_XY`: hue from the XY angle through a cyclic `cmap`, brightness and alpha from the sigmoid-scaled XY magnitude.

Parameters:

- **`values`** (`AnyArray`) –

  The `(N, 3)` vectors.
- **`lo`** (`float`) –

  The XY magnitude at the dark end.
- **`hi`** (`float`) –

  The XY magnitude at the bright end.
- **`cmap`** (`Colormapper`) –

  The cyclic colormap the XY angle maps through.

### color\_range

```python
color_range(chunks: Iterable[AnyArray], *, log_scale: bool = False, robust: bool = True) -> tuple[float, float]
```

The `(lo, hi)` color limits spanning every scalar chunk's own range, from the finite values only; an empty selection gives `(0.0, 1.0)`.

Parameters:

- **`chunks`** (`Iterable[AnyArray]`) –

  The scalar arrays, one per body.
- **`log_scale`** (`bool`, default: `False` ) –

  Keep only the strictly positive values and start at their 1st percentile.
- **`robust`** (`bool`, default: `True` ) –

  Clamp each chunk to its 2-98 percentiles, so a small body keeps its range beside a large one.

### camera\_needs\_reset

```python
camera_needs_reset(position: Sequence[float], focal: Sequence[float], bounds: Sequence[float] | None, *, half_angle_deg: float = 30.0) -> bool
```

Whether the camera has lost the model, with the box behind it or outside its view cone; a camera inside the box always keeps its view.

Parameters:

- **`position`** (`Sequence[float]`) –

  The camera position.
- **`focal`** (`Sequence[float]`) –

  The camera's focal point.
- **`bounds`** (`Sequence[float] | None`) –

  The model box as `(xmin, xmax, ymin, ymax, zmin, zmax)`; missing or non-finite bounds always re-frame.
- **`half_angle_deg`** (`float`, default: `30.0` ) –

  The half angle of the view cone some box corner must fall inside, in degrees.

### plotly\_colorscale

```python
plotly_colorscale(samples: AnyArray, *, invert: bool = False) -> Colorscale
```

A plotly colorscale from sampled RGB(A) rows: evenly-spaced stops paired with `"rgb(r,g,b)"` strings.

### log\_colorbar\_ticks

```python
log_colorbar_ticks(lo: float, hi: float) -> tuple[list[float], list[str]]
```

Decade tick positions spanning a log color range, with their `1e`-notation labels.

### mesh3d\_traces

```python
mesh3d_traces(points: FloatArray, triangles: IntArray, intensity: FloatArray | None, *, name: str, color: str | None = None, opacity: float = 1.0, intensitymode: Association = POINT, colorscale: Colorscale | None = None, cmin: float | None = None, cmax: float | None = None, showscale: bool = False, hovertemplate: str | None = None, customdata: FloatArray | None = None, colorbar: TraceDict | None = None, visible: bool = True) -> list[TraceDict]
```

One plotly `Mesh3d` trace as a plain JSON dict: a body's triangulated surface colored by `intensity`, or by `color` alone.

Parameters:

- **`points`** (`FloatArray`) –

  The `(N, 3)` vertex coordinates.
- **`triangles`** (`IntArray`) –

  The `(M, 3)` vertex indices of each triangle.
- **`intensity`** (`FloatArray | None`) –

  The values the colorscale maps, per vertex or per triangle; None colors by `color` alone.
- **`name`** (`str`) –

  The legend name.
- **`color`** (`str | None`, default: `None` ) –

  A solid color for a body without an intensity.
- **`opacity`** (`float`, default: `1.0` ) –

  The trace opacity.
- **`intensitymode`** (`Association`, default: `POINT` ) –

  Whether `intensity` lives on the vertices or the triangles.
- **`colorscale`** (`Colorscale | None`, default: `None` ) –

  The plotly colorscale `intensity` maps through.
- **`cmin`** (`float | None`, default: `None` ) –

  The intensity at the low end of the colorscale.
- **`cmax`** (`float | None`, default: `None` ) –

  The intensity at the high end of the colorscale.
- **`showscale`** (`bool`, default: `False` ) –

  Draw the colorbar.
- **`hovertemplate`** (`str | None`, default: `None` ) –

  The plotly hover text template.
- **`customdata`** (`FloatArray | None`, default: `None` ) –

  Per-vertex values the hover template reads.
- **`colorbar`** (`TraceDict | None`, default: `None` ) –

  The plotly colorbar settings.
- **`visible`** (`bool`, default: `True` ) –

  Draw the body; a hidden one stays in the legend as `legendonly`.

### outline\_traces

```python
outline_traces(segments: FloatArray, *, name: str = 'outline', color: str = 'black') -> list[TraceDict]
```

One plotly `Scatter3d` lines trace drawing every `(N, 2, 3)` segment, NaN-separated so the segments stay disjoint.

Parameters:

- **`segments`** (`FloatArray`) –

  The `(N, 2, 3)` line segments.
- **`name`** (`str`, default: `'outline'` ) –

  The trace name.
- **`color`** (`str`, default: `'black'` ) –

  The line color.

### list\_fields

```python
list_fields(grid: DataSet) -> list[FieldInfo]
```

Every displayable field of a VTU grid (`GeometryIds` excluded), sorted by display name.

### split\_by\_body

```python
split_by_body(grid: UnstructuredGrid, bodies: Bodies) -> MultiBlock
```

The grid split into one block per named body, keyed by its `entities.json` name via the `GeometryIds` cell array.

### feature\_outline

```python
feature_outline(blocks: MultiBlock) -> PolyData | None
```

The feature and boundary edges of a multiblock's outer surface — the black wireframe drawn over the field, the body outlines on a 2D slice — or None when the surface has none.

### model\_dim\_of

```python
model_dim_of(grid: DataSet) -> int
```

2 for a flat (axisymmetric slice) grid with no Z extent, else 3.

### sample\_colormap

```python
sample_colormap(name: str, count: int = 256) -> FloatArray
```

`count` evenly-spaced RGBA rows from a named colormap, resolved through pyvista's `get_cmap_safe`.

### surface\_triangles

```python
surface_triangles(block: DataSet) -> tuple[FloatArray, IntArray]
```

A block's outer surface as `(points, triangles)`: an `(N, 3)` coordinate array and an `(M, 3)` vertex-index array.

### outline\_segments

```python
outline_segments(edges: PolyData) -> FloatArray
```

The feature edges as an `(N, 2, 3)` array of line segments.
