1# Copyright (c) 2021, Manfred Moitzi 2# License: MIT License 3import math 4from ezdxf.math import ( 5 cubic_bezier_arc_parameters, Matrix44, Vertex, basic_transformation, 6) 7from ezdxf.render import forms 8from .path import Path 9from . import converter 10 11__all__ = [ 12 "unit_circle", "elliptic_transformation", "rect", "ngon", "wedge", "star", 13 "gear" 14] 15 16 17def unit_circle( 18 start_angle: float = 0, 19 end_angle: float = math.tau, 20 segments: int = 1, 21 transform: Matrix44 = None) -> Path: 22 """ Returns an unit circle as a :class:`Path` object, with the center at 23 (0, 0, 0) and the radius of 1 drawing unit. 24 25 The arc spans from the start- to the end angle in counter clockwise 26 orientation. The end angle has to be greater than the start angle and the 27 angle span has to be greater than 0. 28 29 Args: 30 start_angle: start angle in radians 31 end_angle: end angle in radians (end_angle > start_angle!) 32 segments: count of Bèzier-curve segments, default is one segment for 33 each arc quarter (π/2) 34 transform: transformation Matrix applied to the unit circle 35 36 """ 37 path = Path() 38 start_flag = True 39 for start, ctrl1, ctrl2, end in cubic_bezier_arc_parameters( 40 start_angle, end_angle, segments): 41 if start_flag: 42 path.start = start 43 start_flag = False 44 path.curve4_to(end, ctrl1, ctrl2) 45 if transform is None: 46 return path 47 else: 48 return path.transform(transform) 49 50 51def wedge(start_angle: float, 52 end_angle: float, 53 segments: int = 1, 54 transform: Matrix44 = None) -> Path: 55 """ Returns a wedge as a :class:`Path` object, with the center at 56 (0, 0, 0) and the radius of 1 drawing unit. 57 58 The arc spans from the start- to the end angle in counter clockwise 59 orientation. The end angle has to be greater than the start angle and the 60 angle span has to be greater than 0. 61 62 Args: 63 start_angle: start angle in radians 64 end_angle: end angle in radians (end_angle > start_angle!) 65 segments: count of Bèzier-curve segments, default is one segment for 66 each arc quarter (π/2) 67 transform: transformation Matrix applied to the wedge 68 69 """ 70 path = Path() 71 start_flag = True 72 for start, ctrl1, ctrl2, end in cubic_bezier_arc_parameters( 73 start_angle, end_angle, segments): 74 if start_flag: 75 path.line_to(start) 76 start_flag = False 77 path.curve4_to(end, ctrl1, ctrl2) 78 path.line_to((0, 0, 0)) 79 if transform is None: 80 return path 81 else: 82 return path.transform(transform) 83 84 85def elliptic_transformation( 86 center: Vertex = (0, 0, 0), 87 radius: float = 1, 88 ratio: float = 1, 89 rotation: float = 0) -> Matrix44: 90 """ Returns the transformation matrix to transform an unit circle into 91 an arbitrary circular- or elliptic arc. 92 93 Example how to create an ellipse with an major axis length of 3, a minor 94 axis length 1.5 and rotated about 90°:: 95 96 m = elliptic_transformation(radius=3, ratio=0.5, rotation=math.pi / 2) 97 ellipse = shapes.unit_circle(transform=m) 98 99 Args: 100 center: curve center in WCS 101 radius: radius of the major axis in drawing units 102 ratio: ratio of minor axis to major axis 103 rotation: rotation angle about the z-axis in radians 104 105 """ 106 if radius < 1e-6: 107 raise ValueError(f'invalid radius: {radius}') 108 if ratio < 1e-6: 109 raise ValueError(f'invalid ratio: {ratio}') 110 scale_x = radius 111 scale_y = radius * ratio 112 return basic_transformation(center, (scale_x, scale_y, 1), rotation) 113 114 115def rect(width: float = 1, height: float = 1, 116 transform: Matrix44 = None) -> Path: 117 """ Returns a closed rectangle as a :class:`Path` object, with the center at 118 (0, 0, 0) and the given `width` and `height` in drawing units. 119 120 Args: 121 width: width of the rectangle in drawing units, width > 0 122 height: height of the rectangle in drawing units, height > 0 123 transform: transformation Matrix applied to the rectangle 124 125 """ 126 if width < 1e-9: 127 raise ValueError(f"invalid width: {width}") 128 if height < 1e-9: 129 raise ValueError(f"invalid height: {height}") 130 131 w2 = float(width) / 2.0 132 h2 = float(height) / 2.0 133 path = converter.from_vertices( 134 [(w2, h2), (-w2, h2), (-w2, -h2), (w2, h2)], 135 close=True 136 ) 137 if transform is None: 138 return path 139 else: 140 return path.transform(transform) 141 142 143def ngon(count: int, length: float = None, radius: float = 1.0, 144 transform: Matrix44 = None) -> Path: 145 """ Returns a `regular polygon <https://en.wikipedia.org/wiki/Regular_polygon>`_ 146 a :class:`Path` object, with the center at (0, 0, 0). 147 The polygon size is determined by the edge `length` or the circum `radius` 148 argument. If both are given `length` has higher priority. Default size is 149 a `radius` of 1. The ngon starts with the first vertex is on the x-axis! 150 The base geometry is created by function :func:`ezdxf.render.forms.ngon`. 151 152 Args: 153 count: count of polygon corners >= 3 154 length: length of polygon side 155 radius: circum radius, default is 1 156 transform: transformation Matrix applied to the ngon 157 158 """ 159 vertices = forms.ngon(count, length=length, radius=radius) 160 if transform is not None: 161 vertices = transform.transform_vertices(vertices) 162 return converter.from_vertices(vertices, close=True) 163 164 165def star(count: int, r1: float, r2: float, transform: Matrix44 = None) -> Path: 166 """ Returns a `star shape <https://en.wikipedia.org/wiki/Star_polygon>`_ as 167 a :class:`Path` object, with the center at (0, 0, 0). 168 169 Argument `count` defines the count of star spikes, `r1` defines the radius 170 of the "outer" vertices and `r2` defines the radius of the "inner" vertices, 171 but this does not mean that `r1` has to be greater than `r2`. 172 The star shape starts with the first vertex is on the x-axis! 173 The base geometry is created by function :func:`ezdxf.render.forms.star`. 174 175 Args: 176 count: spike count >= 3 177 r1: radius 1 178 r2: radius 2 179 transform: transformation Matrix applied to the star 180 181 """ 182 vertices = forms.star(count, r1=r1, r2=r2) 183 if transform is not None: 184 vertices = transform.transform_vertices(vertices) 185 return converter.from_vertices(vertices, close=True) 186 187 188def gear(count: int, top_width: float, bottom_width: float, height: float, 189 outside_radius: float, transform: Matrix44 = None) -> Path: 190 """ 191 Returns a `gear <https://en.wikipedia.org/wiki/Gear>`_ (cogwheel) shape as 192 a :class:`Path` object, with the center at (0, 0, 0). 193 The base geometry is created by function :func:`ezdxf.render.forms.gear`. 194 195 .. warning:: 196 197 This function does not create correct gears for mechanical engineering! 198 199 Args: 200 count: teeth count >= 3 201 top_width: teeth width at outside radius 202 bottom_width: teeth width at base radius 203 height: teeth height; base radius = outside radius - height 204 outside_radius: outside radius 205 transform: transformation Matrix applied to the gear shape 206 207 """ 208 vertices = forms.gear(count, top_width, bottom_width, height, 209 outside_radius) 210 if transform is not None: 211 vertices = transform.transform_vertices(vertices) 212 return converter.from_vertices(vertices, close=True) 213