Skip to content

Commit

Permalink
Merge pull request #58 from chris-greening/add-custom-origin
Browse files Browse the repository at this point in the history
Add custom origin
  • Loading branch information
chris-greening authored Mar 19, 2023
2 parents 0b15db6 + 46e36a5 commit 03e9b09
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 19 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setuptools.setup(
name="spyrograph",
version="0.15.0",
version="0.16.0",
author="Chris Greening",
author_email="chris@christophergreening.com",
description="Library for drawing spirographs in Python",
Expand Down
7 changes: 4 additions & 3 deletions spyrograph/_cycloid.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

from abc import ABC
from numbers import Number
from typing import List
from typing import List, Tuple

class _Cycloid(ABC):
# pylint: disable=too-few-public-methods
@classmethod
def n_cusps(
cls, R: Number, n: int, thetas: List[Number] = None,
theta_start: Number = None, theta_stop: Number = None,
theta_step: Number = None
theta_step: Number = None, origin: Tuple[Number, Number] = (0, 0)
) -> "Cycloid":
"""Return a cycloid with n number of cusps"""
return cls(
Expand All @@ -21,5 +21,6 @@ def n_cusps(
thetas=thetas,
theta_start=theta_start,
theta_stop=theta_stop,
theta_step=theta_step
theta_step=theta_step,
origin=origin
)
22 changes: 14 additions & 8 deletions spyrograph/_trochoid.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,22 @@
pd = None

class _Trochoid(ABC):
# pylint: disable=too-many-instance-attributes
def __init__(
self, R: Number, r: Number, d: Number, thetas: List[Number] = None,
theta_start: Number = None, theta_stop: Number = None,
theta_step: Number = None
theta_step: Number = None, origin: Tuple[Number, Number] = (0, 0)
) -> None:
self.R = R
self.r = r
self.d = d
self.thetas = self._validate_theta(thetas, theta_start, theta_stop, theta_step)
self.origin = origin

self.x = [self._calculate_x(theta) for theta in self.thetas]
self.y = [self._calculate_y(theta) for theta in self.thetas]
self.x = np.array([self._calculate_x(theta) for theta in self.thetas])
self.y = np.array([self._calculate_y(theta) for theta in self.thetas])
self.x += self.origin[0]
self.y += self.origin[1]
self.coords = list(zip(self.x, self.y, self.thetas))

def plot(self, **kwargs) -> Tuple["matplotlib.matplotlib.Figure", "matplotlib.axes._axes.Axes"]:
Expand Down Expand Up @@ -107,10 +111,11 @@ def trace(
if show_circles:
self._draw_circle(
t=fixed_circle_turtle,
x=0,
y=-self.R,
x=self.origin[0],
y=self.origin[1]-self.R,
radius=self.R
)

while True:
first = True
shape_turtle.up()
Expand Down Expand Up @@ -167,7 +172,7 @@ def _show_full_path(
def _validate_theta(
self, thetas: List[Number], theta_start: Number, theta_stop: Number,
theta_step: Number
) -> "":
) -> "np.array":
# pylint: disable=line-too-long
theta_values = (theta_start, theta_stop, theta_step)
multiple_thetas = thetas is not None and any(theta_values)
Expand All @@ -177,6 +182,7 @@ def _validate_theta(
if theta_step is None:
theta_step = .1
thetas = np.arange(theta_start, theta_stop, theta_step)
thetas = np.array(thetas)
return thetas

def _init_screen(
Expand Down Expand Up @@ -253,8 +259,8 @@ def _draw_circle(

def _draw_rolling_circle(self, t: "turtle.Turtle", theta: Number) -> None:
"""Draw the rolling circle on the screen"""
x=self._circle_offset()*math.cos(theta)
y=self._circle_offset()*math.sin(theta) - self.r
x=self._circle_offset()*math.cos(theta) + self.origin[0]
y=self._circle_offset()*math.sin(theta) - self.r + self.origin[1]
self._draw_circle(
t=t,
x=x,
Expand Down
6 changes: 3 additions & 3 deletions spyrograph/epitrochoid/epicycloid.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
where the distance from the rolling circle is equal to its radius.
"""

from typing import List
from typing import List, Tuple
from numbers import Number

from spyrograph.epitrochoid.epitrochoid import Epitrochoid
Expand All @@ -16,6 +16,6 @@ class Epicycloid(_Cycloid, Epitrochoid):
def __init__(
self, R: Number, r: Number, thetas: List[Number] = None,
theta_start: Number = None, theta_stop: Number = None,
theta_step: Number = None
theta_step: Number = None, origin: Tuple[Number, Number] = (0, 0)
) -> None:
super().__init__(R, r, r, thetas, theta_start, theta_stop, theta_step)
super().__init__(R, r, r, thetas, theta_start, theta_stop, theta_step, origin)
6 changes: 3 additions & 3 deletions spyrograph/hypotrochoid/hypocycloid.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from the rolling circle is equal to the radius of the rolling circle
"""

from typing import List
from typing import List, Tuple
from numbers import Number

from spyrograph.hypotrochoid.hypotrochoid import Hypotrochoid
Expand All @@ -16,6 +16,6 @@ class Hypocycloid(_Cycloid, Hypotrochoid):
def __init__(
self, R: Number, r: Number, thetas: List[Number] = None,
theta_start: Number = None, theta_stop: Number = None,
theta_step: Number = None
theta_step: Number = None, origin: Tuple[Number, Number] = (0, 0)
) -> None:
super().__init__(R, r, r, thetas, theta_start, theta_stop, theta_step)
super().__init__(R, r, r, thetas, theta_start, theta_stop, theta_step, origin)
52 changes: 51 additions & 1 deletion tests/_roulette.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@ def instance(self, thetas):
thetas=thetas
)

def test_custom_origin_offsets(self, thetas):
"""Test custom origin offsets"""
base_obj = self.class_name(
R = 300,
r = 200,
d = 100,
thetas=thetas
)
custom_origin_obj = self.class_name(
R = 300,
r = 200,
d = 100,
thetas=thetas,
origin = (54, -233)
)
assert ((custom_origin_obj.x - base_obj.x).round() == 54.0).all()
assert ((custom_origin_obj.y - base_obj.y).round() == -233.0).all()

def test_theta_range(self) -> None:
"""Test that passing theta start, stop, step creates an expected list of theta values"""
obj = self.class_name(
Expand Down Expand Up @@ -86,6 +104,22 @@ def instance(self, thetas):
thetas=thetas
)

def test_custom_origin_offsets(self, thetas):
"""Test custom origin offsets vertically"""
base_obj = self.class_name(
R = 300,
r = 200,
thetas=thetas
)
custom_origin_obj = self.class_name(
R = 300,
r = 200,
thetas=thetas,
origin = (54, -233)
)
assert ((custom_origin_obj.x - base_obj.x).round() == 54).all()
assert ((custom_origin_obj.y - base_obj.y).round() == -233).all()

def test_theta_range(self) -> None:
"""Test that passing theta start, stop, step creates an expected list of theta values"""
obj = self.class_name(
Expand Down Expand Up @@ -130,4 +164,20 @@ def test_n_cusps(self, thetas):
n = n,
thetas = thetas
)
assert obj.r == obj.R/n
assert obj.r == obj.R/n

def test_n_cusps_custom_origin(self, thetas):
"""Test that custom origin is working with n cusp classmethod"""
base_obj = self.class_name.n_cusps(
R = 300,
n = 2,
thetas=thetas
)
custom_origin_obj = self.class_name.n_cusps(
R = 300,
n = 2,
thetas=thetas,
origin = (54, -233)
)
assert ((custom_origin_obj.x - base_obj.x).round() == 54.0).all()
assert ((custom_origin_obj.y - base_obj.y).round() == -233.0).all()

0 comments on commit 03e9b09

Please sign in to comment.