Skip to content

Commit

Permalink
feat(cylinder): Add Cylinder
Browse files Browse the repository at this point in the history
* feat(cylinder): Add Cylinder3D

Initial class properties and methods

* fix(sphere): Rename Sphere class

Remove '3D' from class name.
Remove '3d' from sphere intersect methods name 
Remove '3D' from sphere_tests

* fix(cone): Rename Cone class

Remove '3D' from class name
Remove '3D' from cone_tests

* fix(cylinder): Rename Cylinder class

Remove '3D' from class name

* feat(cylinder): Add 'center_end' property

+ Fix minor bugs

* feat(tests): Add Cylinder tests

* fix(tests): Fix bugs in Sphere and Cone tests

* feat(cylinder): Add new constructor

Add 'from_start_end' class method
Include new constructor in tests
  • Loading branch information
santiagogaray committed Feb 1, 2020
1 parent 1210f59 commit 76ef3ef
Show file tree
Hide file tree
Showing 7 changed files with 454 additions and 99 deletions.
52 changes: 26 additions & 26 deletions ladybug_geometry/geometry3d/cone.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# coding=utf-8
"""Cone3D"""
"""Cone"""
from __future__ import division

from .pointvector import Point3D, Vector3D

import math


class Cone3D(object):
"""Cone3D object.
class Cone(object):
"""Cone object.
Args:
vertex: A Point3D at the tip of the cone.
Expand All @@ -30,28 +30,28 @@ class Cone3D(object):
__slots__ = ('_vertex', '_axis', '_angle')

def __init__(self, vertex, axis, angle):
"""Initilize Cone3D.
"""Initilize Cone.
"""
assert isinstance(vertex, Point3D), \
"Expected Point3D. Got {}.".format(type(vertex))
assert isinstance(axis, Vector3D), \
"Expected Vector3D. Got {}.".format(type(vertex))
"Expected Vector3D. Got {}.".format(type(axis))
assert angle > 0, 'Cone angle must be greater than 0. Got {}.'.format(angle)
self._vertex = vertex
self._axis = axis
self._angle = angle

@classmethod
def from_dict(cls, data):
"""Create a Cone3D from a dictionary.
"""Create a Cone from a dictionary.
Args:
data: A python dictionary in the following format
.. code-block:: python
{
"type": "Cone3D"
"type": "Cone"
"vertex": (10, 0, 0),
"axis": (0, 0, 1),
"angle": 1.0,
Expand Down Expand Up @@ -107,7 +107,7 @@ def move(self, moving_vec):
Args:
moving_vec: A Vector3D with the direction and distance to move the cone.
"""
return Cone3D(self.vertex.move(moving_vec), self.axis, self.angle)
return Cone(self.vertex.move(moving_vec), self.axis, self.angle)

def rotate(self, axis, angle, origin):
"""Rotate this cone by a certain angle around an axis and origin.
Expand All @@ -119,22 +119,22 @@ def rotate(self, axis, angle, origin):
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.
origin: A Point3D for the origin around which the cone will be rotated.
"""
return Cone3D(self.vertex.rotate(axis, angle, origin),
self.axis.rotate(axis, angle),
self.angle)
return Cone(self.vertex.rotate(axis, angle, origin),
self.axis.rotate(axis, angle),
self.angle)

def rotate_xy(self, angle, origin):
"""Get a cone 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.
origin: A Point3D for the origin around which the cone will be rotated.
"""
return Cone3D(self.vertex.rotate_xy(angle, origin),
self.axis.rotate_xy(angle),
self.angle)
return Cone(self.vertex.rotate_xy(angle, origin),
self.axis.rotate_xy(angle),
self.angle)

def reflect(self, normal, origin):
"""Get a cone reflected across a plane with the input normal vector and origin.
Expand All @@ -144,9 +144,9 @@ def reflect(self, normal, origin):
which the arc will be reflected. THIS VECTOR MUST BE NORMALIZED.
origin: A Point3D representing the origin from which to reflect.
"""
return Cone3D(self.vertex.reflect(normal, origin),
self.axis.reflect(normal),
self.angle)
return Cone(self.vertex.reflect(normal, origin),
self.axis.reflect(normal),
self.angle)

def scale(self, factor, origin=None):
"""Scale a cone by a factor from an origin point.
Expand All @@ -156,21 +156,21 @@ def scale(self, factor, origin=None):
origin: A Point3D representing the origin from which to scale.
If None, it will be scaled from the World origin (0, 0, 0).
"""
return Cone3D(self.vertex.scale(factor, origin),
self.axis * factor,
self.angle)
return Cone(self.vertex.scale(factor, origin),
self.axis * factor,
self.angle)

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

def to_dict(self):
"""Get Cone as a dictionary."""
return {'type': 'Cone3D', 'vertex': self.vertex.to_array(),
return {'type': 'Cone', 'vertex': self.vertex.to_array(),
'axis': self.axis.to_array(), 'angle': self.angle}

def __copy__(self):
return Cone3D(self.vertex, self.axis, self.angle)
return Cone(self.vertex, self.axis, self.angle)

def __key(self):
"""A tuple based on the object properties, useful for hashing."""
Expand All @@ -180,7 +180,7 @@ def __hash__(self):
return hash(self.__key())

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

def __ne__(self, other):
return not self.__eq__(other)
Expand All @@ -190,5 +190,5 @@ def ToString(self):
return self.__repr__()

def __repr__(self):
return 'Cone3D (vertex {}) (axis {}) (angle {}) (height {})'.\
return 'Cone (vertex {}) (axis {}) (angle {}) (height {})'.\
format(self.vertex, self.axis, self.angle, self.height)
208 changes: 208 additions & 0 deletions ladybug_geometry/geometry3d/cylinder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# coding=utf-8
"""Cylinder"""
from __future__ import division

from .pointvector import Point3D, Vector3D

import math


class Cylinder(object):
"""Cylinder object.
Args:
center: A Point3D at the center of the bottom base of the cylinder.
axis: A Vector3D representing the direction and height of the cylinder.
The vector extends from the bottom base center to the top base center.
radius: A number representing the radius of the cylinder.
Properties:
* center
* axis
* radius
* center_end
* diameter
* height
* area
* volume
"""
__slots__ = ('_center', '_axis', '_radius')

def __init__(self, center, axis, radius):
"""Initilize Cylinder.
"""
assert isinstance(center, Point3D), \
"Expected Point3D. Got {}.".format(type(center))
assert isinstance(axis, Vector3D), \
"Expected Vector3D. Got {}.".format(type(axis))
assert radius > 0, \
'Cylinder radius must be greater than 0. Got {}.'.format(radius)
self._center = center
self._axis = axis
self._radius = radius

@classmethod
def from_start_end(cls, p1, p2, radius):
"""Initialize a new cylinder from start and end points.
Args:
p1: The start point of the cylinder, represents the center of the
bottom base of the cylinder.
p2: The end point of the cylinder, represents the center of the top
base of the cylinder
radius: A number representing the radius of the cylinder.
"""
axis = p2 - p1
return cls(p1, axis, radius)

@classmethod
def from_dict(cls, data):
"""Create a Cylinder from a dictionary.
Args:
data: A python dictionary in the following format
.. code-block:: python
{
"type": "Cylinder"
"center": (10, 0, 0),
"axis": (0, 0, 1),
"radius": 1.0,
}
"""
return cls(Point3D.from_array(data['center']),
Vector3D.from_array(data['axis']),
data['radius'])

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

@property
def axis(self):
"""Axis of Cylinder."""
return self._axis

@property
def radius(self):
"""Radius of Cylinder"""
return self._radius

@property
def center_end(self):
"""Center of oposite end of Cylinder."""
return self.center + self.axis

@property
def diameter(self):
"""Diameter of Cylinder"""
return self.radius * 2

@property
def height(self):
"""Height of Cylinder"""
return self.axis.magnitude

@property
def area(self):
"""Surface area of a Cylinder"""
return 2 * math.pi * self.radius * self.height + 2 * math.pi * self.radius ** 2

@property
def volume(self):
"""Volume of a Cylinder"""
return math.pi * self.radius ** 2 * self.height

def move(self, moving_vec):
"""Get a Cylinder that has been moved along a vector.
Args:
moving_vec: A Vector3D with the direction and distance to move the Cylinder.
"""
return Cylinder(self.center.move(moving_vec), self.axis, self.radius)

def rotate(self, axis, angle, origin):
"""Rotate this Cylinder 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 cylinder will be rotated.
"""
return Cylinder(self.center.rotate(axis, angle, origin),
self.axis.rotate(axis, angle),
self.radius)

def rotate_xy(self, angle, origin):
"""Get a Cylinder 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 cylinder will be rotated.
"""
return Cylinder(self.center.rotate_xy(angle, origin),
self.axis.rotate_xy(angle),
self.radius)

def reflect(self, normal, origin):
"""Get a Cylinder 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.
"""
return Cylinder(self.center.reflect(normal, origin),
self.axis.reflect(normal),
self.radius)

def scale(self, factor, origin=None):
"""Scale a Cylinder by a factor from an origin point.
Args:
factor: A number representing how much the Cylinder 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 Cylinder(self.center.scale(factor, origin),
self.axis * factor,
self.radius * factor)

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

def to_dict(self):
"""Get Cylinder as a dictionary."""
return {'type': 'Cylinder', 'center': self.center.to_array(),
'axis': self.axis.to_array(), 'radius': self.radius}

def __copy__(self):
return Cylinder(self.center, self.axis, self.radius)

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

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

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

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

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

def __repr__(self):
return 'Cylinder (center {}) (axis {}) (radius {})'.\
format(self.center, self.axis, self.radius)
Loading

0 comments on commit 76ef3ef

Please sign in to comment.