123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- # Copyright (c) 2010-2019 Manfred Moitzi
- # License: MIT License
- from typing import TYPE_CHECKING, Sequence
- import math
- from .line import ConstructionRay
- from .vector import Vec2
- from .bbox import BoundingBox2d
- from .construct2d import ConstructionTool
- if TYPE_CHECKING:
- from ezdxf.eztypes import Vertex
- HALF_PI = math.pi / 2.
- class ConstructionCircle(ConstructionTool):
- def __init__(self, center: 'Vertex', radius: float = 1.0):
- self.center = Vec2(center)
- self.radius = float(radius)
- if self.radius <= 0.:
- raise ValueError("Radius has to be > 0.")
- @staticmethod
- def from_3p(p1: 'Vertex', p2: 'Vertex', p3: 'Vertex') -> 'ConstructionCircle':
- """ Creates a circle from three points. """
- p1 = Vec2(p1)
- p2 = Vec2(p2)
- p3 = Vec2(p3)
- ray1 = ConstructionRay(p1, p2)
- ray2 = ConstructionRay(p1, p3)
- center_ray1 = ray1.orthogonal(p1.lerp(p2))
- center_ray2 = ray2.orthogonal(p1.lerp(p3))
- center = center_ray1.intersect(center_ray2)
- return ConstructionCircle(center, center.distance(p1))
- @property
- def bounding_box(self) -> 'BoundingBox2d':
- rvec = Vec2((self.radius, self.radius))
- return BoundingBox2d((self.center - rvec, self.center + rvec))
- def move(self, dx: float, dy: float) -> None:
- """
- Move circle about `dx` in x-axis and about `dy` in y-axis.
- Args:
- dx: translation in x-axis
- dy: translation in y-axis
- """
- self.center += Vec2((dx, dy))
- def point_at(self, angle: float) -> Vec2:
- """
- Returns point on circle at `angle` as 2d vector.
- Args:
- angle: angle in radians
- """
- return self.center + Vec2.from_angle(angle, self.radius)
- def inside(self, point: 'Vertex') -> bool:
- """ Test if `point` is inside circle. """
- return self.radius >= self.center.distance(Vec2(point))
- def tangent(self, angle: float) -> ConstructionRay:
- """
- Returns tangent to circle at `angle` as ConstructionRay().
- Args:
- angle: angle in radians
- """
- point_on_circle = self.point_at(angle)
- ray = ConstructionRay(self.center, point_on_circle)
- return ray.orthogonal(point_on_circle)
- def intersect_ray(self, ray: ConstructionRay, abs_tol: float = 1e-12) -> Sequence[Vec2]:
- """
- Returns intersection points for intersection of this circle with `ray` as sequence of 2d points.
- Args:
- ray: intersection ray
- abs_tol: absolute tolerance for tests (e.g. test for tangents)
- Returns: tuple of Vec2()
- tuple contains:
- 0 points .. no intersection
- 1 point .. ray is a tangent on the circle
- 2 points .. ray intersects with the circle
- """
- ortho_ray = ray.orthogonal(self.center)
- intersection_point = ray.intersect(ortho_ray)
- dist = self.center.distance(intersection_point)
- result = []
- if dist < self.radius: # intersect in two points
- if math.isclose(dist, 0., abs_tol=abs_tol): # if ray goes through center point
- angle = ortho_ray.angle
- alpha = HALF_PI
- else:
- # the exact direction of angle (all 4 quadrants Q1-Q4) is important:
- # ortho_ray.angle is only at the center point correct
- angle = (intersection_point - self.center).angle
- alpha = math.acos(intersection_point.distance(self.center) / self.radius)
- result.append(self.point_at(angle + alpha))
- result.append(self.point_at(angle - alpha))
- elif math.isclose(dist, self.radius, abs_tol=abs_tol): # ray is a tangent of circle
- result.append(intersection_point)
- # else no intersection
- return tuple(result)
- def intersect_circle(self, other: 'ConstructionCircle', abs_tol: float = 1e-12) -> Sequence[Vec2]:
- """
- Returns intersection points of two circles as sequence of 2d points.
- Args:
- other: intersection circle
- abs_tol: absolute tolerance for tests (e.g. test for circle touch point)
- Returns: tuple of Vec2()
- tuple contains:
- 0 points .. no intersection
- 1 point .. circle touches the other_circle in one point
- 2 points .. circle intersects with the other_circle
- """
- r1 = self.radius
- r2 = other.radius
- d = self.center.distance(other.center)
- d_max = r1 + r2
- d_min = math.fabs(r1 - r2)
- result = []
- if d_min <= d <= d_max:
- angle = (other.center - self.center).angle
- # if circles touches in one point
- if math.isclose(d, d_max, abs_tol=abs_tol) or math.isclose(d, d_min, abs_tol=abs_tol):
- result.append(self.point_at(angle))
- else: # circles intersect in two points
- alpha = math.acos((r2 ** 2 - r1 ** 2 - d ** 2) / (-2. * r1 * d)) # 'Cosinus-Satz'
- result.append(self.point_at(angle + alpha))
- result.append(self.point_at(angle - alpha))
- return tuple(result)
|