```# coding=utf-8
"""Sphere"""
from __future__ import division

from .pointvector import Point3D
from .plane import Plane
from .arc import Arc3D
from .line import LineSegment3D
from ..intersection3d import intersect_line3d_sphere, intersect_plane_sphere

import math

[docs]class Sphere(object):
"""Sphere object.

Args:
center: A Point3D representing the center of the arc.

Properties:
* center
* min
* max
* diameter
* circumference
* area
* volume
"""

"""Initialize Sphere."""
assert isinstance(center, Point3D), \
"Expected Point3D. Got {}.".format(type(center))
self._center = center

[docs]    @classmethod
def from_dict(cls, data):
"""Create a Sphere from a dictionary.

Args:
data: A python dictionary in the following format

.. code-block:: python

{
"type": "Sphere"
"center": (10, 0, 0),
}
"""

@property
def center(self):
"""Center of sphere."""
return self._center

@property

@property
def min(self):
"""A Point3D for the minimum bounding box vertex around this geometry."""

@property
def max(self):
"""A Point3D for the maximum bounding box vertex around this geometry."""

@property
def diameter(self):
"""Diameter of sphere"""

@property
def circumference(self):
"""Circumference of sphere"""
return 2 * math.pi * self.radius

@property
def area(self):
"""Surface area of sphere"""
return 4 * math.pi * self.radius ** 2

@property
def volume(self):
"""Volume of sphere"""
return 4 / 3 * math.pi * self.radius ** 3

[docs]    def move(self, moving_vec):
"""Get a sphere that has been moved along a vector.

Args:
moving_vec: A Vector3D with the direction and distance to move the sphere.
"""

[docs]    def rotate(self, axis, angle, origin):
"""Rotate this sphere by a certain angle around an axis and origin.

Right hand rule applies:
If axis has a positive orientation, rotation will be clockwise.
If axis has a negative orientation, rotation will be counterclockwise.

Args:
axis: A Vector3D axis representing the axis of rotation.
angle: An angle for rotation in radians.
origin: A Point3D for the origin around which the sphere will be rotated.
"""

[docs]    def rotate_xy(self, angle, origin):
"""Get a sphere that is rotated counterclockwise in the world XY plane by an angle.

Args:
angle: An angle for rotation in radians.
origin: A Point3D for the origin around which the sphere will be rotated.
"""

[docs]    def reflect(self, normal, origin):
"""Get a sphere reflected across a plane with the input normal vector and origin.

Args:
normal: A Vector3D representing the normal vector for the plane across
which the arc will be reflected. THIS VECTOR MUST BE NORMALIZED.
origin: A Point3D representing the origin from which to reflect.
"""

[docs]    def scale(self, factor, origin=None):
"""Scale a sphere by a factor from an origin point.

Args:
factor: A number representing how much the sphere should be scaled.
origin: A Point3D representing the origin from which to scale.
If None, it will be scaled from the World origin (0, 0, 0).
"""
return Sphere(self.center.scale(factor, origin), self.radius * factor)

[docs]    def intersect_plane(self, plane):
"""Get the intersection of a plane with this Sphere object

Args:
plane: A Plane object.

Returns:
Arc3D representing a full circle if it exists.
None if no full intersection exists.
"""
ip = intersect_plane_sphere(plane, self)  # ip = [center pt, vector, radius]
return None if ip is None or isinstance(ip, Point3D) else \
Arc3D(Plane(ip[1], ip[0]), ip[2])

[docs]    def intersect_line_ray(self, line_ray):
"""Get the intersection between this Sphere object and a Ray2D/LineSegment2D.

Args:
line_ray: A LineSegment3D or Ray3D that will be extended infinitely
for intersection.

Returns:
A LineSegment3D object if a full intersection exists.
A Point if a tangent intersection exists.
None if no full intersection exists.
"""
il = intersect_line3d_sphere(line_ray, self)
return None if il is None else \
il if isinstance(il, Point3D) else \
LineSegment3D.from_end_points(il[0], il[1])

[docs]    def duplicate(self):
"""Get a copy of this object."""
return self.__copy__()

[docs]    def to_dict(self):
"""Get Sphere as a dictionary."""
return {'type': 'Sphere',
'center': self.center.to_array(),

def __copy__(self):

def __key(self):
"""A tuple based on the object properties, useful for hashing."""

def __hash__(self):
return hash(self.__key())

def __eq__(self, other):
return isinstance(other, Sphere) and self.__key() == other.__key()

def __ne__(self, other):
return not self.__eq__(other)

[docs]    def ToString(self):
"""Overwrite .NET ToString."""
return self.__repr__()

def __repr__(self):