Skip to content

plot_distances

Plotting helpers for distance and plane visualization.

AxisPointsLineConfig

Bases: NamedTuple

Configuration for axis points and line visualization.

KeyPointsStyle

Bases: NamedTuple

Styling configuration for key points visualization.

LineStyle

Bases: NamedTuple

Styling configuration for line visualization.

SpanStyle

Bases: NamedTuple

Styling configuration for span points visualization.

add_axis_points_line

add_axis_points_line(
    fig: Figure,
    axis_points: ndarray,
    *,
    axis_points_labels: Iterable[str] | None = None,
    show_axis_points: bool = True,
    axis_points_colors: tuple[str, str] | None = None,
    axis_points_size: float | None = None,
    line_name: str | None = None,
    line_color: str | None = None,
    line_width: float | None = None,
    line_dash: str | None = None,
    config: AxisPointsLineConfig | None = None,
) -> None

Add axis points and connecting line to a figure.

This function adds two traces to the figure: 1. Axis points as markers with text labels (optional) 2. A line connecting the axis points (always plotted)

Parameters:

Name Type Description Default

fig

Figure

Plotly figure to update.

required

axis_points

ndarray

Array of shape (2, 3) containing the axis points.

required

axis_points_labels

Iterable[str] | None

Optional labels for axis points. Defaults to ["pt0", "pt1"].

None

show_axis_points

bool

Whether to display axis points markers. Default is True.

True

axis_points_colors

tuple[str, str] | None

Two colors for axis points markers (legacy parameter).

None

axis_points_size

float | None

Marker size for axis points (legacy parameter).

None

line_name

str | None

Trace name for axis points line (legacy parameter).

None

line_color

str | None

Line color for axis points line (legacy parameter).

None

line_width

float | None

Line width for axis points line (legacy parameter).

None

line_dash

str | None

Dash style for axis points line (legacy parameter).

None

config

AxisPointsLineConfig | None

Configuration object for styling. Overrides individual parameters.

None

Raises:

Type Description
ValueError

If axis_points shape is not (2, 3).

Source code in src/mechaphlowers/plotting/plot_distances.py
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
def add_axis_points_line(
    fig: go.Figure,
    axis_points: np.ndarray,
    *,
    axis_points_labels: Iterable[str] | None = None,
    show_axis_points: bool = True,
    axis_points_colors: tuple[str, str] | None = None,
    axis_points_size: float | None = None,
    line_name: str | None = None,
    line_color: str | None = None,
    line_width: float | None = None,
    line_dash: str | None = None,
    config: AxisPointsLineConfig | None = None,
) -> None:
    """Add axis points and connecting line to a figure.

    This function adds two traces to the figure:
    1. Axis points as markers with text labels (optional)
    2. A line connecting the axis points (always plotted)

    Args:
        fig: Plotly figure to update.
        axis_points: Array of shape (2, 3) containing the axis points.
        axis_points_labels: Optional labels for axis points. Defaults to ["pt0", "pt1"].
        show_axis_points: Whether to display axis points markers. Default is True.
        axis_points_colors: Two colors for axis points markers (legacy parameter).
        axis_points_size: Marker size for axis points (legacy parameter).
        line_name: Trace name for axis points line (legacy parameter).
        line_color: Line color for axis points line (legacy parameter).
        line_width: Line width for axis points line (legacy parameter).
        line_dash: Dash style for axis points line (legacy parameter).
        config: Configuration object for styling. Overrides individual parameters.

    Raises:
        ValueError: If axis_points shape is not (2, 3).
    """
    # Validate inputs
    axis_points = np.asarray(axis_points)
    _validate_axis_points(axis_points)

    # Set defaults
    if axis_points_labels is None:
        axis_points_labels = ["pt0", "pt1"]

    # Build configuration from individual parameters if config not provided
    if config is None:
        kp_style = KeyPointsStyle(
            color=axis_points_colors or ("green", "blue"),
            size=axis_points_size if axis_points_size is not None else 10.0,
        )
        line_style = LineStyle(
            name=line_name or "Key points line",
            color=line_color or "green",
            width=line_width if line_width is not None else 4.0,
            dash=line_dash or "dash",
        )
        config = AxisPointsLineConfig(
            axis_points_style=kp_style,
            line_style=line_style,
            show_axis_points=show_axis_points,
        )

    # Add traces
    if config.show_axis_points:
        _add_axis_points_trace(
            fig, axis_points, axis_points_labels, config.axis_points_style
        )
    _add_line_trace(fig, axis_points, config.line_style)

add_span_points

add_span_points(
    fig: Figure,
    span_points: ndarray,
    *,
    span_name: str | None = None,
    span_color: str | None = None,
    span_marker_size: float | None = None,
    config: SpanStyle | None = None,
) -> None

Add span points to a figure.

Parameters:

Name Type Description Default

fig

Figure

Plotly figure to update.

required

span_points

ndarray

Array of shape (N, 3) containing span points along the line.

required

span_name

str | None

Trace name for span points (legacy parameter).

None

span_color

str | None

Marker color for span points (legacy parameter).

None

span_marker_size

float | None

Marker size for span points (legacy parameter).

None

config

SpanStyle | None

Configuration object for styling. Overrides individual parameters.

None

Raises:

Type Description
ValueError

If span_points is not an (N, 3) array.

Source code in src/mechaphlowers/plotting/plot_distances.py
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
def add_span_points(
    fig: go.Figure,
    span_points: np.ndarray,
    *,
    span_name: str | None = None,
    span_color: str | None = None,
    span_marker_size: float | None = None,
    config: SpanStyle | None = None,
) -> None:
    """Add span points to a figure.

    Args:
        fig: Plotly figure to update.
        span_points: Array of shape (N, 3) containing span points along the line.
        span_name: Trace name for span points (legacy parameter).
        span_color: Marker color for span points (legacy parameter).
        span_marker_size: Marker size for span points (legacy parameter).
        config: Configuration object for styling. Overrides individual parameters.

    Raises:
        ValueError: If span_points is not an (N, 3) array.
    """
    # Validate inputs
    span_points = np.asarray(span_points)
    _validate_span_points(span_points)

    # Build configuration from individual parameters if config not provided
    if config is None:
        config = SpanStyle(
            name=span_name or "Span",
            color=span_color or "orange",
            marker_size=span_marker_size
            if span_marker_size is not None
            else 3.0,
        )

    # Add trace
    _add_span_trace(fig, span_points, config)

plot_distance_engine

plot_distance_engine(
    distance_engine: DistanceEngine,
    distance_result: DistanceResult | None = None,
    fig: Figure | None = None,
    title_addendum: str | None = None,
    plane_scale: float = 10.0,
    plane_grid_size: int = 3,
    axis_labels: Iterable[str] | None = None,
    axis_color: str = 'darkviolet',
    curve_color: str = 'darkblue',
    plane_color: str = 'gold',
    projection_color: str = 'firebrick',
    force_layout: bool = True,
    show_axis_points: bool = True,
    show_curve: bool = True,
    show_plane: bool = True,
    show_distance_result: bool = True,
    show_projections: bool = True,
) -> Figure

Plot DistanceEngine components including axis, curve, plane, and distance result.

This function creates a comprehensive 3D visualization of the DistanceEngine, showing the axis line, curve points, optional plane, and distance calculations.

Parameters:

Name Type Description Default

distance_engine

DistanceEngine

The DistanceEngine instance to visualize.

required

distance_result

DistanceResult | None

Optional DistanceResult from plane_distance computation.

None

fig

Figure | None

Existing figure to add to, or None to create a new figure.

None

show_axis_points

bool

Whether to show axis start/end point markers.

True

show_curve

bool

Whether to show the curve points.

True

show_plane

bool

Whether to show the distance plane (requires distance_result).

True

show_distance_result

bool

Whether to show distance result points and vectors.

True

show_projections

bool

Whether to show projection lines (requires distance_result).

True

plane_scale

float

Scale of the plane visualization.

10.0

plane_grid_size

int

Grid density for plane mesh.

3

axis_labels

Iterable[str] | None

Labels for axis start and end points.

None

axis_color

str

Color for axis line and points.

'darkviolet'

curve_color

str

Color for curve points.

'darkblue'

plane_color

str

Color for plane surface.

'gold'

projection_color

str

Color for projection vectors.

'firebrick'

title_addendum

str | None

Optional string to append to legend groups for uniqueness.

None

Returns:

Type Description
Figure

Plotly figure with all requested components.

Raises:

Type Description
AttributeError

If distance_engine is missing required attributes.

ValueError

If show_distance_result is True but distance_result is None.

Examples:

1
2
3
4
5
>>> engine = DistanceEngine()
>>> engine.add_span_frame(axis_start, axis_end)
>>> engine.add_curves(curve_points)
>>> result = engine.plane_distance(point_base)
>>> fig = plot_distance_engine(engine, result, show_plane=True)
Source code in src/mechaphlowers/plotting/plot_distances.py
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
def plot_distance_engine(
    distance_engine: DistanceEngine,
    distance_result: DistanceResult | None = None,
    fig: go.Figure | None = None,
    title_addendum: str | None = None,
    plane_scale: float = 10.0,
    plane_grid_size: int = 3,
    axis_labels: Iterable[str] | None = None,
    axis_color: str = "darkviolet",
    curve_color: str = "darkblue",
    plane_color: str = "gold",
    projection_color: str = "firebrick",
    force_layout: bool = True,
    show_axis_points: bool = True,
    show_curve: bool = True,
    show_plane: bool = True,
    show_distance_result: bool = True,
    show_projections: bool = True,
) -> go.Figure:
    """Plot DistanceEngine components including axis, curve, plane, and distance result.

    This function creates a comprehensive 3D visualization of the DistanceEngine,
    showing the axis line, curve points, optional plane, and distance calculations.

    Args:
        distance_engine: The DistanceEngine instance to visualize.
        distance_result: Optional DistanceResult from plane_distance computation.
        fig: Existing figure to add to, or None to create a new figure.
        show_axis_points: Whether to show axis start/end point markers.
        show_curve: Whether to show the curve points.
        show_plane: Whether to show the distance plane (requires distance_result).
        show_distance_result: Whether to show distance result points and vectors.
        show_projections: Whether to show projection lines (requires distance_result).
        plane_scale: Scale of the plane visualization.
        plane_grid_size: Grid density for plane mesh.
        axis_labels: Labels for axis start and end points.
        axis_color: Color for axis line and points.
        curve_color: Color for curve points.
        plane_color: Color for plane surface.
        projection_color: Color for projection vectors.
        title_addendum: Optional string to append to legend groups for uniqueness.

    Returns:
        Plotly figure with all requested components.

    Raises:
        AttributeError: If distance_engine is missing required attributes.
        ValueError: If show_distance_result is True but distance_result is None.

    Examples:
        >>> engine = DistanceEngine()
        >>> engine.add_span_frame(axis_start, axis_end)
        >>> engine.add_curves(curve_points)
        >>> result = engine.plane_distance(point_base)
        >>> fig = plot_distance_engine(engine, result, show_plane=True)
    """
    # Validate inputs
    if not hasattr(distance_engine, "axis_start") or not hasattr(
        distance_engine, "axis_end"
    ):
        raise AttributeError(
            "DistanceEngine must have axis_start and axis_end defined. "
            "Call add_span_frame() first."
        )

    if show_distance_result and distance_result is None:
        raise ValueError(
            "distance_result must be provided when show_distance_result=True"
        )

    # Create or reuse figure
    title_addendum = f" - {title_addendum}" if title_addendum else ""

    if fig is None:
        fig = go.Figure()

    if force_layout:
        fig.update_layout(
            scene=dict(
                xaxis_title="X (m)",
                yaxis_title="Y (m)",
                zaxis_title="Z (m)",
                aspectmode="data",
            ),
            title="Distance Analysis Engine Visualization",
        )

    # 1. Plot axis line and points
    axis_points = np.array(
        [distance_engine.axis_start, distance_engine.axis_end]
    )
    if axis_labels is None:
        axis_labels = ["span origin", "span X axis"]

    add_axis_points_line(
        fig,
        axis_points,
        axis_points_labels=axis_labels,
        show_axis_points=show_axis_points,
        line_color=axis_color,
        line_width=4.0,
        axis_points_colors=(axis_color, axis_color),
    )

    # 2. Plot curve points
    if show_curve and hasattr(distance_engine, "curve_points"):
        add_span_points(
            fig,
            distance_engine.curve_points,
            span_name="Curve",
            span_color=curve_color,
            span_marker_size=4.0,
        )

    # 4. Plot plane if requested
    if show_plane:
        # Compute plane mesh
        u_plane = distance_engine.u_plane
        v_plane = distance_engine.v_plane
        point_on_plane = distance_engine.point_base

        if distance_result is not None:
            plane_scale = max(
                distance_result.distance_3d * 2, plane_scale
            )  # Scale plane based on distance for better visualization

        # Create plane grid
        x_plane, y_plane, z_plane = meshgrid_plane(
            u_plane,
            v_plane,
            point_on_plane,
            scale_plane=plane_scale,
            grid_size_plane=plane_grid_size,
        )

        # Add plane surface
        add_surface(
            fig, title_addendum, plane_color, x_plane, y_plane, z_plane
        )

    # 3. Plot distance result components
    if show_distance_result and distance_result is not None:
        # Add the two key points
        plot_distance_points(
            fig=fig,
            distance_points=np.array(
                [distance_result.point_base, distance_result.point_target]
            ),
            color=["darkred", "darkred"],
            symbol=["cross", "cross"],
            text=["Obstacle", "Intersection"],
            title_addendum=title_addendum,
        )

        # Add 3D distance line
        plot_3d_line(
            fig,
            distance_result.point_base,
            distance_result.point_target,
            distance_result.distance_3d,
            title_addendum,
        )
        # Add distance text label
        midpoint = (
            distance_result.point_base + distance_result.point_target
        ) / 2
        plot_text(fig, distance_result.distance_3d, title_addendum, midpoint)

        # Add projection vectors if requested
        if show_projections:
            # Get projection points
            u_proj, v_proj = distance_result.projection_points(
                distance_result.point_base
            )

            # U,V projection line
            plot_projected_distances(
                fig,
                distance_result.point_base,
                distance_result.signed_distance_projection_u,
                "U Projection",
                title_addendum,
                projection_color,
                u_proj,
            )
            plot_projected_distances(
                fig,
                distance_result.point_base,
                distance_result.signed_distance_projection_v,
                "V Projection",
                title_addendum,
                projection_color,
                v_proj,
            )

    return fig

plot_text

plot_text(
    fig: Figure, text: float, title_addendum: str, position
) -> None

Add text label to figure.

Args: text: DistanceResult containing distance_3d attribute for label text. fig: Plotly figure to add text to. title_addendum: String to append to legend group for uniqueness. position: 3D coordinates for text label placement.

Source code in src/mechaphlowers/plotting/plot_distances.py
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
def plot_text(
    fig: go.Figure,
    text: float,
    title_addendum: str,
    position,
) -> None:
    """Add text label to figure.

    Args:
    text: DistanceResult containing distance_3d attribute for label text.
    fig: Plotly figure to add text to.
    title_addendum: String to append to legend group for uniqueness.
    position: 3D coordinates for text label placement.

    """

    fig.add_trace(
        go.Scatter3d(
            x=[position[0]],
            y=[position[1]],
            z=[position[2]],
            mode=distance_text.scatter_mode,
            text=[distance_text.text_format(text)],
            textposition=distance_text.text_position,
            textfont=distance_text.text_font,
            name=distance_text.name,
            legendgroup=str(distance_text.legend_group) + title_addendum,
            showlegend=distance_text.show_legend,
            hoverinfo=distance_text.hoverinfo,
        )
    )