# Copyright 2026 Capytaine developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Abstract base class for floating bodies."""
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Literal, List, Union, Tuple
import numpy as np
from capytaine.bodies.dofs import (
add_dofs_labels_to_vector,
add_dofs_labels_to_matrix,
)
from capytaine.bodies.visualization import show_3d
[docs]
class AbstractBody(ABC):
"""Abstract base class for FloatingBody and Multibody.
Defines the shared interface that both single bodies and collections
of bodies must implement.
Subclasses must provide the following attributes (either as instance
attributes set in __init__, or as properties/cached_properties):
* name: str
* mesh
* lid_mesh
* mesh_including_lid
* hull_mask
* dofs: dict
* mass
* center_of_mass
"""
name: str
[docs]
@abstractmethod
def rename(self, name): ...
def __lt__(self, other: AbstractBody) -> bool:
"""Arbitrary order. The point is to sort together the problems involving the same body."""
return self.name < other.name
# --- Dof labelling (identical in both subclasses) ---
[docs]
def add_dofs_labels_to_vector(self, vector):
"""Helper function turning a bare vector into a vector labelled by the name of the dofs of the body,
to be used for instance for the computation of RAO."""
return add_dofs_labels_to_vector(self.dofs.keys(), vector)
[docs]
def add_dofs_labels_to_matrix(self, matrix):
"""Helper function turning a bare matrix into a matrix labelled by the name of the dofs of the body,
to be used for instance for the computation of RAO."""
return add_dofs_labels_to_matrix(self.dofs.keys(), matrix)
# --- Body joining (identical in both subclasses) ---
def __add__(self, body_to_add: AbstractBody) -> AbstractBody:
return self.join_bodies(body_to_add)
[docs]
def join_bodies(*bodies, name=None) -> Multibody: # noqa: F821
from capytaine.bodies.multibodies import Multibody
return Multibody(bodies, name=name)
# --- Abstract methods (different implementations) ---
[docs]
@abstractmethod
def integrate_pressure(self, pressure): ...
[docs]
@abstractmethod
def minimal_computable_wavelength(self): ...
[docs]
@abstractmethod
def first_irregular_frequency_estimate(self, *args, **kwargs): ...
[docs]
@abstractmethod
def compute_hydrostatic_stiffness(self, *, rho=1000.0, g=9.81): ...
[docs]
@abstractmethod
def compute_rigid_body_inertia(self, rho=1000.0): ...
@abstractmethod
def _check_dofs_shape_consistency(self): ...
# --- Geometric transforms ---
[docs]
@abstractmethod
def translated(self, shift, *, name=None) -> AbstractBody: ...
[docs]
def translated_x(self, dx: float, *, name=None) -> AbstractBody:
return self.translated([dx, 0.0, 0.0], name=name)
[docs]
def translated_y(self, dy: float, *, name=None) -> AbstractBody:
return self.translated([0.0, dy, 0.0], name=name)
[docs]
def translated_z(self, dz: float, *, name=None) -> AbstractBody:
return self.translated([0.0, 0.0, dz], name=name)
[docs]
@abstractmethod
def rotated_with_matrix(self, R, *, name=None) -> AbstractBody: ...
[docs]
def rotated_x(self, angle: float, *, name=None) -> AbstractBody:
c, s = np.cos(angle), np.sin(angle)
R = np.array([[1, 0, 0], [0, c, -s], [0, s, c]])
return self.rotated_with_matrix(R, name=name)
[docs]
def rotated_y(self, angle: float, *, name=None) -> AbstractBody:
c, s = np.cos(angle), np.sin(angle)
R = np.array([[c, 0, s], [0, 1, 0], [-s, 0, c]])
return self.rotated_with_matrix(R, name=name)
[docs]
def rotated_z(self, angle: float, *, name=None) -> AbstractBody:
c, s = np.cos(angle), np.sin(angle)
R = np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
return self.rotated_with_matrix(R, name=name)
[docs]
@abstractmethod
def mirrored(self, plane: Literal['xOz', 'yOz']) -> AbstractBody: ...
[docs]
@abstractmethod
def clipped(self, *, origin, normal, name=None) -> AbstractBody: ...
[docs]
@abstractmethod
def immersed_part(self, free_surface=0.0, *, sea_botton=None, water_depth=None, name=None) -> AbstractBody: ...
[docs]
@abstractmethod
def copy(self, name=None) -> AbstractBody: ...
# --- Display ---
@abstractmethod
def __str__(self): ...
@abstractmethod
def __short_str__(self): ...
def __repr__(self):
return str(self)
[docs]
def show(self, *, backend=None, **kwargs):
"""Visualize the mesh using the specified backend.
Parameters
----------
backend : str, optional
Visualization backend to use. Options are 'pyvista' or 'matplotlib'.
By default, try several until an installed one is found.
**kwargs
Additional keyword arguments passed to the visualization backend.
See :mod:`~capytaine.meshes.visualization`
Returns
-------
object
Visualization object returned by the backend (e.g., matplotlib figure).
Raises
------
NotImplementedError
If the specified backend is not supported.
"""
return show_3d(self, backend=backend, **kwargs)
[docs]
def show_pyvista(self, **kwargs):
"""
Equivalent to show(backend="pyvista").
See also :func:`~capytaine.bodies.visualization.show_pyvista`
"""
return self.show(backend="pyvista", **kwargs)
[docs]
def show_matplotlib(self, **kwargs):
"""
Equivalent to show(backend="matplotlib").
See also :func:`~capytaine.bodies.visualization.show_matplotlib`
"""
return self.show(backend="matplotlib", **kwargs)