def test_complex_ellipse_with_spline_intersection(self): ellipse = ConstructionEllipse(center=(0, 0), major_axis=(3, 0), ratio=0.5) bspline = BSpline([(-4, -4), (-2, -1), (2, 1), (4, 4)]) p1 = ellipse.flattening(distance=0.01) p2 = bspline.flattening(distance=0.01) res = intersect_polylines_2d(Vec2.list(p1), Vec2.list(p2)) assert len(res) == 2
def from_arc(cls, entity: 'DXFGraphic') -> 'Spline': """ Create a new SPLINE entity from a CIRCLE, ARC or ELLIPSE entity. The new SPLINE entity has no owner, no handle, is not stored in the entity database nor assigned to any layout! """ dxftype = entity.dxftype() if dxftype == 'ELLIPSE': ellipse = cast('Ellipse', entity).construction_tool() elif dxftype == 'CIRCLE': ellipse = ConstructionEllipse.from_arc( center=entity.dxf.get('center', NULLVEC), radius=abs(entity.dxf.get('radius', 1.0)), extrusion=entity.dxf.get('extrusion', Z_AXIS), ) elif dxftype == 'ARC': ellipse = ConstructionEllipse.from_arc( center=entity.dxf.get('center', NULLVEC), radius=abs(entity.dxf.get('radius', 1.0)), extrusion=entity.dxf.get('extrusion', Z_AXIS), start_angle=entity.dxf.get('start_angle', 0), end_angle=entity.dxf.get('end_angle', 360)) else: raise TypeError('CIRCLE, ARC or ELLIPSE entity required.') spline = Spline.new(dxfattribs=entity.graphic_properties(), doc=entity.doc) s = BSpline.from_ellipse(ellipse) spline.dxf.degree = s.degree spline.dxf.flags = Spline.RATIONAL spline.control_points = s.control_points spline.knots = s.knots() spline.weights = s.weights() return spline
def test_dxfattribs(): e = ConstructionEllipse() attribs = e.dxfattribs() assert attribs['center'] == (0, 0, 0) assert attribs['major_axis'] == (1, 0, 0) assert 'minor_axis' not in attribs assert attribs['extrusion'] == (0, 0, 1) assert attribs['ratio'] == 1.0 assert attribs['start_param'] == 0 assert attribs['end_param'] == math.tau
def test_dxfattribs(): e = ConstructionEllipse() attribs = e.dxfattribs() assert attribs["center"] == (0, 0, 0) assert attribs["major_axis"] == (1, 0, 0) assert "minor_axis" not in attribs assert attribs["extrusion"] == (0, 0, 1) assert attribs["ratio"] == 1.0 assert attribs["start_param"] == 0 assert attribs["end_param"] == math.tau
def test_swap_axis_full_ellipse(): ellipse = ConstructionEllipse( major_axis=(5, 0, 0), ratio=2, ) assert ellipse.minor_axis.isclose((0, 10, 0)) ellipse.swap_axis() assert ellipse.ratio == 0.5 assert ellipse.major_axis == (0, 10, 0) assert ellipse.minor_axis == (-5, 0, 0) assert ellipse.start_param == 0 assert ellipse.end_param == math.pi * 2
def test_tangents(): e = ConstructionEllipse(center=(3, 3), major_axis=(2, 0), ratio=0.5, start_param=0, end_param=math.pi * 1.5) params = list(e.params(7)) result = [(0.0, 1.0, 0.0), (-0.894427190999916, 0.447213595499958, 0.0), (-1.0, 3.061616997868383e-17, 0.0), (-0.894427190999916, -0.4472135954999579, 0.0), (-2.4492935982947064e-16, -1.0, 0.0), (0.8944271909999159, -0.44721359549995804, 0.0), (1.0, 0.0, 0.0)] for v, r in zip(e.tangents(params), result): assert v.isclose(r)
def test_rational_spline_from_elliptic_arc_has_expected_parameters(): ellipse = ConstructionEllipse( center=(1, 1), major_axis=(2, 0), ratio=0.5, start_param=0, end_param=math.pi / 2, ) spline = rational_bspline_from_ellipse(ellipse) assert spline.degree == 2 cpoints = spline.control_points assert len(cpoints) == 3 assert cpoints[0].isclose((3, 1, 0)) assert cpoints[1].isclose((3, 2, 0)) assert cpoints[2].isclose((1, 2, 0)) weights = spline.weights() assert len(weights) == 3 assert weights[0] == 1.0 assert weights[1] == math.cos(math.pi / 4) assert weights[2] == 1.0 # as BSpline constructor() s2 = BSpline.from_ellipse(ellipse) assert spline.control_points == s2.control_points
def draw_elliptic_arc_entity_3d(self, entity: DXFGraphic) -> None: dxf, dxftype = entity.dxf, entity.dxftype() properties = self._resolve_properties(entity) if dxftype in {'CIRCLE', 'ARC'}: center = dxf.center # ocs transformation in .from_arc() radius = dxf.radius if dxftype == 'CIRCLE': start_angle = 0 end_angle = 360 else: start_angle = dxf.start_angle end_angle = dxf.end_angle e = ConstructionEllipse.from_arc(center, radius, dxf.extrusion, start_angle, end_angle) elif dxftype == 'ELLIPSE': e = cast(Ellipse, entity).construction_tool() else: raise TypeError(dxftype) # Approximate as 3D polyline segments = int((e.end_param - e.start_param) / math.tau * self.circle_approximation_count) points = list( e.vertices( linspace(e.start_param, e.end_param, max(4, segments + 1)))) self.out.start_path() for a, b in zip(points, points[1:]): self.out.draw_line(a, b, properties) self.out.end_path()
def apply_construction_tool(self, e: ConstructionEllipse) -> 'Ellipse': """ Set ELLIPSE data from construction tool :class:`ezdxf.math.ConstructionEllipse`. """ self.update_dxf_attribs(e.dxfattribs()) return self # floating interface
def test_add_ellipse(): from ezdxf.math import ConstructionEllipse ellipse = ConstructionEllipse(center=(3, 0), major_axis=(1, 0), ratio=0.5, start_param=0, end_param=math.pi) path = Path() tools.add_ellipse(path, ellipse) assert path.start == (4, 0) assert path.end == (2, 0) # set start point to end of ellipse path = Path(start=(2, 0)) # add reversed ellipse, by default the start of # an empty path is set to the ellipse start tools.add_ellipse(path, ellipse, reset=False) assert path.start == (2, 0) assert path.end == (4, 0) path = Path() # add a line segment from (0, 0) to start of ellipse tools.add_ellipse(path, ellipse, reset=False) assert path.start == (0, 0) assert path.end == (2, 0)
def test_from_arc(): ellipse = ConstructionEllipse.from_arc(center=(2, 2, 2), radius=3) assert ellipse.center == (2, 2, 2) assert ellipse.major_axis == (3, 0, 0) assert ellipse.ratio == 1 assert ellipse.start_param == 0 assert math.isclose(ellipse.end_param, math.tau)
def apply_construction_tool(self, e: ConstructionEllipse) -> None: """ Set ELLIPSE data from construction tool :class:`ezdxf.math.ConstructionEllipse`. .. versionadded:: 0.13 """ self.update_dxf_attribs(e.dxfattribs())
def test_vertices(): e = ConstructionEllipse(center=(3, 3), major_axis=(2, 0), ratio=0.5, start_param=0, end_param=math.pi * 1.5) params = list(e.params(7)) result = [ (5.0, 3.0, 0.0), (4.414213562373095, 3.7071067811865475, 0.0), (3.0, 4.0, 0.0), (1.585786437626905, 3.7071067811865475, 0.0), (1.0, 3.0, 0.0), (1.5857864376269046, 2.2928932188134525, 0.0), (3.0, 2.0, 0.0), ] for v, r in zip(e.vertices(params), result): assert v.isclose(r) v1, v2 = e.vertices([0, math.tau]) assert v1 == v2
def test_default_init(): e = ConstructionEllipse() assert e.center == (0, 0, 0) assert e.major_axis == (1, 0, 0) assert e.minor_axis == (0, 1, 0) assert e.extrusion == (0, 0, 1) assert e.ratio == 1.0 assert e.start_param == 0 assert e.end_param == math.tau
def from_circle(cls, circle: 'Circle', segments: int = 1) -> 'Path': """ Returns a :class:`Path` from a :class:`~ezdxf.entities.Circle`. """ path = cls() ellipse = ConstructionEllipse.from_arc( center=circle.dxf.center, radius=circle.dxf.radius, extrusion=circle.dxf.extrusion, ) path.add_ellipse(ellipse, segments=segments, reset=True) return path
def test_from_ellipse(): from ezdxf.entities import Ellipse from ezdxf.math import ConstructionEllipse e = ConstructionEllipse(center=(3, 0), major_axis=(1, 0), ratio=0.5, start_param=0, end_param=math.pi) ellipse = Ellipse.new() ellipse.apply_construction_tool(e) path = Path.from_ellipse(ellipse) assert path.start == (4, 0) assert path.end == (2, 0)
def bezier4p_from_ellipse(func, count): ellipse = ConstructionEllipse( center=(1, 2), major_axis=(2, 0), ratio=0.5, start_param=0, end_param=math.tau, ) for _ in range(count): list(func(ellipse))
def construction_tool(self) -> ConstructionEllipse: """Returns construction tool :class:`ezdxf.math.ConstructionEllipse`.""" dxf = self.dxf return ConstructionEllipse( dxf.center, dxf.major_axis, dxf.extrusion, dxf.ratio, dxf.start_param, dxf.end_param, )
def add_arc_edge(edge): x, y, *_ = edge.center # from_arc() requires OCS data: ellipse = ConstructionEllipse.from_arc( center=(x, y, elevation), radius=edge.radius, extrusion=extrusion, start_angle=edge.start_angle, end_angle=edge.end_angle, ) tools.add_ellipse(path, ellipse, reset=not bool(path))
def _from_circle(circle: Circle, **kwargs) -> "Path": segments = kwargs.get("segments", 1) path = Path() radius = abs(circle.dxf.radius) if radius > 1e-12: ellipse = ConstructionEllipse.from_arc( center=circle.dxf.center, radius=radius, extrusion=circle.dxf.extrusion, ) tools.add_ellipse(path, ellipse, segments=segments, reset=True) return path
def test_rational_spline_from_complex_elliptic_arc(): ellipse = ConstructionEllipse( center=(49.64089977339618, 36.43095770602131, 0.0), major_axis=(16.69099826506408, 6.96203799241026, 0.0), ratio=0.173450304570581, start_param=5.427509144462117, end_param=7.927025930557775, ) curves = list(cubic_bezier_from_ellipse(ellipse)) assert curves[0].control_points[0].isclose(ellipse.start_point) assert curves[1].control_points[-1].isclose(ellipse.end_point)
def add_ellipse_edge(edge): ocs_ellipse = edge.construction_tool() # ConstructionEllipse has WCS representation: ellipse = ConstructionEllipse( center=wcs(ocs_ellipse.center.replace(z=elevation)), major_axis=wcs(ocs_ellipse.major_axis), ratio=ocs_ellipse.ratio, extrusion=extrusion, start_param=ocs_ellipse.start_param, end_param=ocs_ellipse.end_param, ) tools.add_ellipse(path, ellipse, reset=not bool(path))
def _from_circle(circle: Circle, **kwargs) -> 'Path': segments = kwargs.get('segments', 1) path = Path() radius = abs(circle.dxf.radius) if not math.isclose(radius, 0): ellipse = ConstructionEllipse.from_arc( center=circle.dxf.center, radius=radius, extrusion=circle.dxf.extrusion, ) tools.add_ellipse(path, ellipse, segments=segments, reset=True) return path
def test_get_start_and_end_vertex(): ellipse = ConstructionEllipse( center=(1, 2, 3), major_axis=(4, 3, 0), extrusion=(0, 0, -1), ratio=.7, start_param=math.pi / 2, end_param=math.pi, ) start, end = list(ellipse.vertices([ ellipse.start_param, ellipse.end_param, ])) # test values from BricsCAD assert start.isclose(Vec3(3.1, -0.8, 3), abs_tol=1e-6) assert end.isclose(Vec3(-3, -1, 3), abs_tol=1e-6) # for convenience, but vertices() is much more efficient: assert ellipse.start_point.isclose(Vec3(3.1, -0.8, 3), abs_tol=1e-6) assert ellipse.end_point.isclose(Vec3(-3, -1, 3), abs_tol=1e-6)
def construction_tool(self) -> ConstructionEllipse: """Returns ConstructionEllipse() for the OCS representation.""" return ConstructionEllipse( center=Vec3(self.center), major_axis=Vec3(self.major_axis), extrusion=Vec3(0, 0, 1), ratio=self.ratio, # 1. ConstructionEllipse() is always in ccw orientation # 2. Start- and end params are always stored in ccw orientation start_param=self.start_param, end_param=self.end_param, )
def from_arc(cls, arc: 'Arc', segments: int = 1) -> 'Path': """ Returns a :class:`Path` from an :class:`~ezdxf.entities.Arc`. """ path = cls() ellipse = ConstructionEllipse.from_arc( center=arc.dxf.center, radius=arc.dxf.radius, extrusion=arc.dxf.extrusion, start_angle=arc.dxf.start_angle, end_angle=arc.dxf.end_angle, ) path.add_ellipse(ellipse, segments=segments, reset=True) return path
def test_params_from_vertices_random(): center = Vector.random(5) major_axis = Vector.random(5) extrusion = Vector.random() ratio = 0.75 e = ConstructionEllipse(center, major_axis, extrusion, ratio) params = [random.uniform(0.0001, math.tau - 0.0001) for _ in range(20)] vertices = e.vertices(params) new_params = e.params_from_vertices(vertices) for expected, param in zip(params, new_params): assert math.isclose(expected, param) # This creates the same vertex as v1 and v2 v1, v2 = e.vertices([0, math.tau]) assert v1.isclose(v2) # This should create the same param for v1 and v2, but # floating point inaccuracy produces unpredictable results: p1, p2 = e.params_from_vertices((v1, v2)) assert math.isclose(p1, 0, abs_tol=1e-9) or math.isclose( p1, math.tau, abs_tol=1e-9) assert math.isclose(p2, 0, abs_tol=1e-9) or math.isclose( p2, math.tau, abs_tol=1e-9)
def test_rational_spline_from_elliptic_arc_has_same_end_points(): ellipse = ConstructionEllipse( center=(1, 1), major_axis=(2, 0), ratio=0.5, start_param=math.radians(30), end_param=math.radians(330), ) start_point = ellipse.start_point end_point = ellipse.end_point spline = rational_bspline_from_ellipse(ellipse) assert start_point.isclose(spline.control_points[0]) assert end_point.isclose(spline.control_points[-1])
def bulge_to(p1: Vec3, p2: Vec3, bulge: float): if p1.isclose(p2): return center, start_angle, end_angle, radius = bulge_to_arc(p1, p2, bulge) ellipse = ConstructionEllipse.from_arc( center, radius, Z_AXIS, math.degrees(start_angle), math.degrees(end_angle), ) curves = list(cubic_bezier_from_ellipse(ellipse)) if curves[0].control_points[0].isclose(p2): curves = _reverse_bezier_curves(curves) self.add_curves(curves)
def test_swap_axis_arbitrary_params(): random_tests_count = 100 random.seed(0) for _ in range(random_tests_count): ellipse = ConstructionEllipse( # avoid (0, 0, 0) as major axis major_axis=(non_zero_random(), non_zero_random(), 0), ratio=2, start_param=random.uniform(0, math.tau), end_param=random.uniform(0, math.tau), extrusion=(0, 0, random.choice((1, -1))), ) # Test if coordinates of start- and end point stay at the same location # before and after swapping axis. start_point = ellipse.start_point end_point = ellipse.end_point minor_axis = ellipse.minor_axis ellipse.swap_axis() assert ellipse.major_axis.isclose(minor_axis, abs_tol=1e-9) assert ellipse.start_point.isclose(start_point, abs_tol=1e-9) assert ellipse.end_point.isclose(end_point, abs_tol=1e-9)