# Source code for skspatial.objects.cylinder

"""Module for the Cylinder class."""
from __future__ import annotations

from typing import Optional
from typing import Tuple

import numpy as np
from mpl_toolkits.mplot3d import Axes3D

from skspatial.objects._base_spatial import _BaseSpatial
from skspatial.objects._mixins import _ToPointsMixin
from skspatial.objects.line import Line
from skspatial.objects.plane import Plane
from skspatial.objects.point import Point
from skspatial.objects.vector import Vector
from skspatial.typing import array_like

class Cylinder(_BaseSpatial, _ToPointsMixin):
"""
A cylinder in space.

The cylinder is defined by a point at its base, a vector along its axis, and a radius.

Parameters
----------
point : array_like
Centre of the cylinder base.
vector : array_like
Normal vector of the cylinder base (the vector along the cylinder axis).
The length of the cylinder is the length of this vector.
This is the radius of the circular base.

Attributes
----------
point : Point
Centre of the cylinder base.
vector : Vector
Normal vector of the cylinder base.
dimension : int
Dimension of the cylinder.

Raises
------
ValueError
If the point or vector are not 3D.
If the vector is all zeros.

Examples
--------
>>> from skspatial.objects import Cylinder

>>> Cylinder([0, 0], [1, 0, 0], 1)
Traceback (most recent call last):
...
ValueError: The point must be 3D.

>>> Cylinder([0, 0, 0], [1, 0], 1)
Traceback (most recent call last):
...
ValueError: The vector must be 3D.

>>> Cylinder([0, 0, 0], [0, 0, 0], 1)
Traceback (most recent call last):
...
ValueError: The vector must not be the zero vector.

>>> Cylinder([0, 0, 0], [0, 0, 1], 0)
Traceback (most recent call last):
...
ValueError: The radius must be positive.

>>> cylinder = Cylinder([0, 0, 0], [0, 0, 1], 1)

>>> cylinder
Cylinder(point=Point([0, 0, 0]), vector=Vector([0, 0, 1]), radius=1)

>>> cylinder.point
Point([0, 0, 0])
>>> cylinder.vector
Vector([0, 0, 1])
1
>>> cylinder.dimension
3

"""

def __init__(self, point: array_like, vector: array_like, radius: float):

self.point = Point(point)
self.vector = Vector(vector)

if self.point.dimension != 3:
raise ValueError("The point must be 3D.")

if self.vector.dimension != 3:
raise ValueError("The vector must be 3D.")

if self.vector.is_zero():
raise ValueError("The vector must not be the zero vector.")

raise ValueError("The radius must be positive.")

self.dimension = self.point.dimension

def __repr__(self) -> str:

repr_point = np.array_repr(self.point)
repr_vector = np.array_repr(self.vector)

[docs] def volume(self) -> np.float64: r""" Return the volume of the cylinder. The volume :math:V of a cylinder with radius :math:r and length :math:l is .. math:: V = \pi r^2 l Returns ------- np.float64 Volume of the cylinder. Examples -------- >>> from skspatial.objects import Cylinder >>> Cylinder([0, 0, 0], [0, 0, 1], 1).volume().round(5) 3.14159 The length of the vector sets the length of the cylinder. >>> Cylinder([0, 0, 0], [0, 0, 2], 1).volume().round(5) 6.28319 """ return np.pi * self.radius**2 * self.length()
[docs] def intersect_line( self, line: Line, n_digits: Optional[int] = None, infinite: bool = True, ) -> Tuple[Point, Point]: """ Intersect the cylinder with a 3D line. By default, this method treats the cylinder as infinite along its axis (i.e., without caps). Parameters ---------- line : Line Input 3D line. n_digits : int, optional Additional keywords passed to :func:round. This is used to round the coefficients of the quadratic equation. infinite : bool If True, the cylinder is treated as infinite along its axis (i.e., without caps). Returns ------- point_a, point_b: Point The two intersection points of the line with the cylinder, if they exist. Raises ------ ValueError If the line is not 3D. If the line does not intersect the cylinder at one or two points. References ---------- https://mrl.cs.nyu.edu/~dzorin/rendering/lectures/lecture3/lecture3.pdf Examples -------- >>> from skspatial.objects import Line, Cylinder >>> cylinder = Cylinder([0, 0, 0], [0, 0, 1], 1) >>> line = Line([0, 0, 0], [1, 0, 0]) Intersection with an infinite cylinder. >>> cylinder.intersect_line(line) (Point([-1., 0., 0.]), Point([1., 0., 0.])) >>> line = Line([1, 2, 3], [1, 2, 3]) >>> point_a, point_b = cylinder.intersect_line(line) >>> point_a.round(3) Point([-0.447, -0.894, -1.342]) >>> point_b.round(3) Point([0.447, 0.894, 1.342]) >>> cylinder.intersect_line(Line([0, 0], [1, 2])) Traceback (most recent call last): ... ValueError: The line must be 3D. >>> cylinder.intersect_line(Line([0, 0, 2], [0, 0, 1])) Traceback (most recent call last): ... ValueError: The line does not intersect the cylinder. >>> cylinder.intersect_line(Line([2, 0, 0], [0, 1, 1])) Traceback (most recent call last): ... ValueError: The line does not intersect the cylinder. Intersection with a finite cylinder. >>> point_a, point_b = cylinder.intersect_line(Line([0, 0, 0], [0, 0, 1]), infinite=False) >>> point_a Point([0., 0., 0.]) >>> point_b Point([0., 0., 1.]) >>> cylinder = Cylinder([0, 0, 0], [0, 0, 5], 1) >>> point_a, point_b = cylinder.intersect_line(Line([0, 0, 0], [1, 0, 1]), infinite=False) >>> point_a Point([0., 0., 0.]) >>> point_b Point([1., 0., 1.]) """ if line.dimension != 3: raise ValueError("The line must be 3D.") if infinite: return _intersect_line_with_infinite_cylinder(self, line, n_digits) return _intersect_line_with_finite_cylinder(self, line, n_digits)
[docs] def plot_3d(self, ax_3d: Axes3D, n_along_axis: int = 100, n_angles: int = 30, **kwargs) -> None: """ Plot a 3D cylinder. Parameters ---------- ax_3d : Axes3D Instance of :class:~mpl_toolkits.mplot3d.axes3d.Axes3D. n_along_axis : int Number of intervals along the axis of the cylinder. n_angles : int Number of angles distributed around the circle. kwargs : dict, optional Additional keywords passed to :meth:~mpl_toolkits.mplot3d.axes3d.Axes3D.plot_surface. Examples -------- .. plot:: :include-source: >>> import matplotlib.pyplot as plt >>> from mpl_toolkits.mplot3d import Axes3D >>> from skspatial.objects import Cylinder >>> fig = plt.figure() >>> ax = fig.add_subplot(111, projection='3d') >>> cylinder = Cylinder([5, 3, 1], [1, 0, 1], 2) >>> cylinder.plot_3d(ax, alpha=0.2) >>> cylinder.point.plot_3d(ax, s=100) """ X, Y, Z = self.to_mesh(n_along_axis, n_angles) ax_3d.plot_surface(X, Y, Z, **kwargs)