def dividing_points(self, n=None, prefix='a'): """ Create the list of Points that divide the Bipoint in n parts. :param n: the number of parts (so it will create n - 1 points) n must be greater or equal to 1 :type n: int """ if not (is_number(n) and is_integer(n)): raise TypeError('n must be an integer') if not n >= 1: raise ValueError('n must be greater or equal to 1') x0 = self.points[0].x x1 = self.points[1].x xstep = (x1 - x0) / n x_list = [x0 + (i + 1) * xstep for i in range(int(n - 1))] y0 = self.points[0].y y1 = self.points[1].y ystep = (y1 - y0) / n y_list = [y0 + (i + 1) * ystep for i in range(int(n - 1))] if self.three_dimensional: z0 = self.points[0].z z1 = self.points[1].z zstep = (z1 - z0) / n z_list = [z0 + (i + 1) * zstep for i in range(int(n - 1))] else: z_list = ['undefined' for i in range(int(n - 1))] return [ Point(x, y, z, prefix + str(i + 1)) for i, (x, y, z) in enumerate(zip(x_list, y_list, z_list)) ]
def project(point, matrix): x = sum([ pc * coord for pc, coord in zip(matrix[0], point.coordinates) ]) y = sum([ pc * coord for pc, coord in zip(matrix[1], point.coordinates) ]) return Point(x, y, point.name)
def isobarycenter(self, name='automatic'): if self.three_dimensional: zval = mean([v.z for v in self.vertices]) else: zval = 'undefined' return Point(mean([v.x for v in self.vertices]), mean([v.y for v in self.vertices]), z=zval, name=name)
def midpoint(self, name='automatic'): """Bipoint's midpoint.""" if self.three_dimensional: zval = (self.points[0].z + self.points[1].z) / 2 else: zval = 'undefined' return Point((self.points[0].x + self.points[1].x) / 2, (self.points[0].y + self.points[1].y) / 2, z=zval, name=name)
def add(self, other, new_endpoint_name='automatic'): if not isinstance(other, Bipoint): raise TypeError('Can only add a Bipoint to another Bipoint. ' 'Found {} instead.'.format(repr(other))) if self.three_dimensional: zval = self.points[1].z + other.Δz else: zval = 'undefined' return Bipoint( self.points[0], Point(self.points[1].x + other.Δx, self.points[1].y + other.Δy, z=zval, name=new_endpoint_name))
def __init__(self, tail, head, allow_zero_length=True): """ A Bipoint can be created from a pair of Points or a Point + a Vector. :param tail: the first Point of the Bipoint :type tail: Point :param head: the second Point of the Bipoint. If a Vector is provided, the second Point will be calculated using the first Point and this Vector. :type head: Point or Vector :param allow_zero_length: whether zero length Bipoints are allowed or not (default True). :type allow_zero_length: bool """ if not isinstance(tail, Point): raise TypeError('First argument must be a Point, found {} ' 'instead.'.format(repr(tail))) if not isinstance(head, (Point, Vector)): raise TypeError('Second argument must be a Point or a Vector, ' 'found {} instead.'.format(repr(head))) self._three_dimensional = tail.three_dimensional \ or head.three_dimensional if isinstance(head, Vector): if self._three_dimensional: zval = tail.z + head.z else: zval = 'undefined' head = Point(tail.x + head.x, tail.y + head.y, zval) if (not allow_zero_length and tail.coordinates == head.coordinates): msg = 'Explicitly disallowed creation of a zero-length {}.'\ .format(type(self).__name__) raise ZERO_OBJECTS_ERRORS[type(self).__name__](msg) self._points = [tail, head] self._Δx = self.points[1].x - self.points[0].x self._Δy = self.points[1].y - self.points[0].y self._Δz = self.points[1].z - self.points[0].z
def point_at(self, position, name='automatic'): """ A Point aligned with the Bipoint, at provided position. The Bipoint's length is the length unit of position. Hence, position 0 matches points[0], position 1 matches points[1], position 0.5 matches the midpoint, position 0.75 is three quarters on the way from points[0] to points[1], position 2 is a Point that makes points[1] the middle between it and points[0], position -1 makes points[0] the middle between it and points[1]. :param position: a number :type position: number :param name: the name to give to the Point :type name: str """ if not is_number(position): raise TypeError( 'position must be a number, found {} instead.'.format( type(position))) k = Number(position) if k == 0: return self.points[0] elif k == 1: return self.points[1] else: if self.three_dimensional: zval = (self.points[0].z + (self.points[1].z - self.points[0].z) * k) else: zval = 'undefined' return Point( (self.points[0].x + (self.points[1].x - self.points[0].x) * k), (self.points[0].y + (self.points[1].y - self.points[0].y) * k), z=zval, name=name)
def __init__(self, start_vertex=None, name=None, base_length=Number('1.5'), equal_legs_length=Number('1'), mark_equal_sides=True, use_mark='||', draw_vertices=False, label_vertices=True, thickness='thick', color=None, rotation_angle=0, winding=None, sloped_sides_labels=True): r""" Initialize Isosceles Triangle :param start_vertex: the vertex to start to draw the Right Triangle (default (0; 0)) :type start_vertex: Point :param name: the name of the Triangle, like ABC. Can be either None (the names will be automatically created), or a string of the letters to use to name the vertices. Only single letters are supported as Points' names so far (at Polygon's creation). See issue #3. :type name: None or str :param base_length: the length of the base of the IsoscelesTriangle, that will be used to calculate the coordinates of its vertices :type base_length: a number :param equal_legs_length: the length of the equal legs of the IsoscelesTriangle, that will be used to calculate the coordinates of its vertices :type equal_legs_length: a number :param mark_equal_sides: if True (default), all three sides will be automatically marked with the same symbol. :type mark_equal_sides: bool :param draw_vertices: whether to actually draw, or not, the vertices :type draw_vertices: bool :param label_vertices: whether to label, or not, the vertices :type label_vertices: bool :param thickness: the thickness of the Triangle's sides :type thickness: str :param color: the color of the Triangle's sides :type color: str :param rotate: the angle of rotation around isobarycenter :type rotate: int """ if start_vertex is None: start_vertex = Point(0, 0) self._base_length = Number(base_length) self._equal_legs_length = Number(equal_legs_length) v1 = Point(base_length + start_vertex.x, start_vertex.y) v2 = Point( base_length / 2, (equal_legs_length**2 - Number('0.25') * base_length**2).sqrt().rounded(Number('0.001')) + start_vertex.y) if (winding == 'clockwise' or (winding is None and config.polygons.DEFAULT_WINDING == 'clockwise')): start_vertex, v1 = v1, start_vertex Triangle.__init__(self, start_vertex, v1, v2, name=name, draw_vertices=draw_vertices, label_vertices=label_vertices, thickness=thickness, color=color, rotation_angle=rotation_angle, winding=winding, sloped_sides_labels=sloped_sides_labels) self._type = 'IsoscelesTriangle' if mark_equal_sides: self.sides[1].mark = self.sides[2].mark = use_mark
def __init__(self, *vertices, name=None, draw_vertices=False, label_vertices=True, thickness='thick', color=None, rotation_angle=0, winding=None, do_cycle=True, sloped_sides_labels=True): r""" Initialize Polygon :param vertices: the vertices of the Polygon :type vertices: a list of at least three Points :param name: the name of the Polygon, like ABCDE for a pentagon. Can be either None (the names of the provided Points will be kept), or a string of the letters to use to rename the provided Points. Only single letters are supported as Points' names so far (at Polygon's creation). See issue #3. :type name: None or str :param draw_vertices: whether to actually draw, or not, the vertices :type draw_vertices: bool :param label_vertices: whether to label, or not, the vertices :type label_vertices: bool :param thickness: the thickness of the Polygon's sides :type thickness: str :param color: the color of the Polygon's sides :type color: str :param rotate: the angle of rotation around isobarycenter :type rotate: int :param winding: force the winding to be either 'clockwise' or 'anticlockwise'. If left to None (default), doesn't force anything, the winding will be either forced by the value of config.DEFAULT_POLYGON_WINDING, or if it is None too, then the winding will be deduced from the given vertices' order. :type winding: None or a str ('clockwise' or 'anticlockwise') """ self.thickness = thickness self.color = color self.do_cycle = do_cycle self.draw_vertices = draw_vertices self.label_vertices = label_vertices if len(vertices) <= 2: raise ValueError('At least three Points are required to be able ' 'to build a Polygon. Got only {} positional ' 'arguments, though.'.format(len(vertices))) if any([not isinstance(v, Point) for v in vertices]): for i, v in enumerate(vertices): if not isinstance(v, Point): raise TypeError('Only Points must be provided in order to ' 'build a Polygon. Got a {} as positional ' 'argument #{}.'.format(type(v), i)) if not is_number(rotation_angle): raise TypeError('Expected a number as rotation angle, got a {} ' 'instead.'.format(type(rotation_angle))) if name is not None: if len(name) != len(vertices): raise ValueError('The number of provided vertices ({}) does ' 'not match the number of Points\' names ' '({}).'.format(len(vertices), len(name))) if (winding is None and config.polygons.DEFAULT_WINDING is not None): winding = config.polygons.DEFAULT_WINDING if winding is not None: check_winding(winding) self._reverted_winding = False if shoelace_formula(*vertices) < 0: if winding == 'anticlockwise': vertices = vertices[::-1] self._reverted_winding = True self.winding = 'anticlockwise' else: self.winding = 'clockwise' else: if winding == 'clockwise': vertices = vertices[::-1] self._reverted_winding = True self.winding = 'clockwise' else: self.winding = 'anticlockwise' self._vertices = [] self._three_dimensional = False for i, v in enumerate(vertices): if name is None: vname = v.name else: vname = name[i] if v.name == v.label: lbl = 'default' else: lbl = v.label if v.three_dimensional: self._three_dimensional = True zval = v.z else: zval = 'undefined' self._vertices.append( Point(v.x, v.y, z=zval, name=vname, shape=v.shape, label=lbl, color=v.color, shape_scale=v.shape_scale)) if rotation_angle: center = self.isobarycenter() for i in range(len(self._vertices)): self._vertices[i] = self._vertices[i].rotate( center=center, angle=rotation_angle, rename='keep_name') self._sides = [] shifted_vertices = deepcopy(self._vertices) shifted_vertices += [shifted_vertices.pop(0)] for (v0, v1) in zip(self._vertices, shifted_vertices): self._sides += [ LineSegment(v0, v1, label_winding=self.winding, locked_label=True) ] self._angles = [] left_shifted_vertices = deepcopy(self._vertices) left_shifted_vertices = \ [left_shifted_vertices.pop(-1)] + left_shifted_vertices for (v0, v1, v2) in zip(left_shifted_vertices, self._vertices, shifted_vertices): self._angles += [Angle(v2, v1, v0)] for i in range(len(self._vertices)): u = Vector(self._vertices[i], left_shifted_vertices[i]) v = Vector(self._vertices[i], shifted_vertices[i]) if self.winding == 'clockwise': u, v = v, u self._vertices[i].label_position = \ tikz_approx_position(u.bisector(v).slope360) if len(self._sides) in POLYGONS_TYPES: self._type = POLYGONS_TYPES[len(self._sides)] else: self._type = \ '{n}-sided Polygon'.format(n=str(len(self._sides))) self.sloped_sides_labels = sloped_sides_labels if (self._reverted_winding and config.polygons.ENABLE_MISMATCH_WINDING_WARNING): warnings.warn('Changed the order of Points to comply with forced ' 'winding ({}) for {}.'.format(winding, repr(self)))
def __init__(self, *vertices, name=None, draw_vertices=False, label_vertices=True, thickness='thick', color=None): r""" Initialize Polyhedron. :param vertices: the vertices of the Polyhedron :type vertices: a list of at least three Points :param name: the name of the Polyhedron, like ABCDEFGH. Can be either None (the names will be automatically created), or a string of the letters to use to name the vertices. Only single letters are supported as Points' names so far (at Polyhedron's creation). See issue #3. :type name: None or str :param draw_vertices: whether to actually draw, or not, the vertices :type draw_vertices: bool :param label_vertices: whether to label, or not, the vertices :type label_vertices: bool :param thickness: the thickness of the Polyhedron's edges :type thickness: str :param color: the color of the Polyhedron's edges :type color: str """ if len(vertices) <= 3: raise ValueError('At least four Points are required to be able ' 'to build a Polyhedron. Found only {} positional ' 'arguments, though.'.format(len(vertices))) if any([not (isinstance(v, Point) and v.three_dimensional) for v in vertices]): for i, v in enumerate(vertices): if not isinstance(v, Point): raise TypeError('Only Points must be provided in order to ' 'build a Polyhedron. Yet found {} as ' 'positional argument #{}.' .format(type(v), i)) if not v.three_dimensional: raise TypeError('Points used to build a Polyhedron must ' 'be three-dimensional Points. Found a ' 'two-dimensional Point instead: {}.' .format(repr(v))) if name is not None: if not isinstance(name, str): raise TypeError('name must be a str, found {} instead.' .format(repr(name))) if not len(name) == len(vertices): raise ValueError('A polyhedron\'s name must contain as many ' 'letters as the polyhedron\'s number of ' 'vertices, yet found {} letters (name: {}) ' 'and {} vertices.' ''.format(len(name), repr(name), len(vertices))) self.thickness = thickness self.color = color self.draw_vertices = draw_vertices self.label_vertices = label_vertices self._vertices = [] for i, v in enumerate(vertices): if name is None: vname = v.name else: vname = name[i] if v.name == v.label: lbl = 'default' else: lbl = v.label self._vertices.append(Point(v.x, v.y, v.z, name=vname, shape=v.shape, label=lbl, color=v.color, shape_scale=v.shape_scale)) self._init_faces() self._edges = [] for F in self.faces: for s in F.sides: if s not in self._edges: self._edges.append(s)
def __init__(self, start_vertex=None, name=None, side_length=Number('1'), build_angle=60, mark_equal_sides=True, use_mark='||', draw_vertices=False, label_vertices=True, thickness='thick', color=None, rotation_angle=0, winding=None, sloped_sides_labels=True): r""" Initialize Rhombus :param start_vertex: the vertex to start to draw the Rhombus (default (0; 0)) :type start_vertex: Point :param name: the name of the Rhombus, like ABCD. Can be either None (the names will be automatically created), or a string of the letters to use to name the vertices. Only single letters are supported as Points' names so far (at Polygon's creation). See issue #3. :type name: None or str :param side_length: the length that will be used to calculate the coordinates of the vertices used to build the Rhombus :type side_length: a number :param build_angle: one of the interior angles of the Rhombus. :type build_angle: any number :param mark_equal_sides: if True (default), all four sides will be automatically marked with the same symbol. :type mark_equal_sides: bool :param draw_vertices: whether to actually draw, or not, the vertices :type draw_vertices: bool :param label_vertices: whether to label, or not, the vertices :type label_vertices: bool :param thickness: the thickness of the Quadrilateral's sides :type thickness: str :param color: the color of the Quadrilateral's sides :type color: str :param rotate: the angle of rotation around isobarycenter :type rotate: int """ if start_vertex is None: start_vertex = Point(0, 0) self._side_length = Number(side_length) if is_number(build_angle): self._build_angle = build_angle else: raise TypeError( 'Expected an integer as build_angle, found {}.'.format( type(build_angle))) x = (side_length * Number(str(cos(radians(build_angle / 2)))))\ .rounded(Number('0.001')) y = (side_length * Number(str(sin(radians(build_angle / 2)))))\ .rounded(Number('0.001')) v1 = Point(x + start_vertex.x, -y + start_vertex.y) v2 = Point(2 * x + start_vertex.x, start_vertex.y) v3 = Point(x + start_vertex.x, y + start_vertex.y) Quadrilateral.__init__(self, start_vertex, v1, v2, v3, name=name, draw_vertices=draw_vertices, label_vertices=label_vertices, thickness=thickness, color=color, rotation_angle=rotation_angle, winding=winding, sloped_sides_labels=sloped_sides_labels) self._type = 'Rhombus' Equilateral.__init__(self, mark_equal_sides=mark_equal_sides, use_mark=use_mark)
def __init__(self, start_vertex=None, dimensions=None, name=None, draw_vertices=False, label_vertices=True, thickness='thick', color=None): r""" Initialize Right Cuboid :param start_vertex: the vertex to start to draw the Right Cuboid (default (0; 0)) :type start_vertex: Point (must be a 3D Point) :param dimensions: (width, depth, height) :type dimensions: tuple (of numbers) :param name: the name of the RightCuboid, like ABCDEFGH. Can be either None (the names will be automatically created), or a string of the letters to use to name the vertices. Only single letters are supported as Points' names so far (at Polyhedra's creation). See issue #3. :type name: None or str :param draw_vertices: whether to actually draw, or not, the vertices :type draw_vertices: bool :param label_vertices: whether to label, or not, the vertices :type label_vertices: bool :param thickness: the thickness of the RightCuboid's edges :type thickness: str :param color: the color of the RightCuboid's edges :type color: str """ if start_vertex is None: start_vertex = Point(0, 0, 0) if not isinstance(start_vertex, Point): raise TypeError( 'start_vertex must be a Point; found {} instead.'.format( repr(start_vertex))) if not start_vertex.three_dimensional: raise TypeError( 'start_vertex must be a three-dimensional Point. ' 'Found this two-dimensional Point instead: {}.'.format( repr(start_vertex))) if not isinstance(dimensions, (tuple, list)): raise TypeError('dimensions must be a tuple or a list. Found {} ' 'instead.'.format(dimensions)) if not len(dimensions) == 3: raise TypeError('dimensions must have a length of 3. Found {} ' 'instead.'.format(dimensions)) x, y, z = start_vertex.coordinates width, depth, height = dimensions vertices = [ start_vertex, Point(x + width, y, z), Point(x + width, y + depth, z), Point(x, y + depth, z), Point(x, y, z + height), Point(x + width, y, z + height), Point(x + width, y + depth, z + height), Point(x, y + depth, z + height) ] Polyhedron.__init__(self, *vertices, name=name, draw_vertices=draw_vertices, label_vertices=label_vertices, thickness=thickness, color=color) self._width, self._depth, self._height = width, depth, height self._labels = None self._edges_to_label = {}
def __init__(self, start_vertex=None, name=None, leg1_length=Number(2), leg2_length=Number(1), mark_right_angle=True, draw_vertices=False, label_vertices=True, thickness='thick', color=None, rotation_angle=0, winding=None, sloped_sides_labels=True): r""" Initialize Right Triangle :param start_vertex: the vertex to start to draw the Right Triangle (default (0; 0)) :type start_vertex: Point :param name: the name of the Triangle, like ABC. Can be either None (the names will be automatically created), or a string of the letters to use to name the vertices. Only single letters are supported as Points' names so far (at Polygon's creation). See issue #3. :type name: None or str :param leg1_length: the leg1's length that will be used to calculate the coordinates of the vertices used to build the RightTriangle :type leg1_length: a number :param leg2_length: the leg2's length that will be used to calculate the coordinates of the vertices used to build the RightTriangle :type leg2_length: a number :param mark_right_angle: if True (default), the right angle will be automatically marked as right angle. :type mark_right_angle: bool :param draw_vertices: whether to actually draw, or not, the vertices :type draw_vertices: bool :param label_vertices: whether to label, or not, the vertices :type label_vertices: bool :param thickness: the thickness of the Triangle's sides :type thickness: str :param color: the color of the Triangle's sides :type color: str :param rotate: the angle of rotation around isobarycenter :type rotate: int """ if start_vertex is None: start_vertex = Point(0, 0) # Accepted type for leg1's and leg2's lengths is number, will be # checked at vertices' instanciations. v1 = Point(leg1_length + start_vertex.x, start_vertex.y) v2 = Point(leg1_length + start_vertex.x, leg2_length + start_vertex.y) if (winding == 'clockwise' or (winding is None and config.polygons.DEFAULT_WINDING == 'clockwise')): start_vertex, v2 = v2, start_vertex Triangle.__init__(self, start_vertex, v1, v2, name=name, draw_vertices=draw_vertices, label_vertices=label_vertices, thickness=thickness, color=color, rotation_angle=rotation_angle, winding=winding, sloped_sides_labels=sloped_sides_labels) self._type = 'RightTriangle' if mark_right_angle: self.right_angle.decoration = AngleDecoration(thickness=thickness) self.right_angle.mark_right = True
def __init__(self, start_vertex=None, name=None, side_length=Number('1'), mark_equal_sides=True, use_mark='||', draw_vertices=False, label_vertices=True, thickness='thick', color=None, rotation_angle=0, winding=None, sloped_sides_labels=True): r""" Initialize Equilateral Triangle :param start_vertex: the vertex to start to draw the Right Triangle (default (0; 0)) :type start_vertex: Point :param name: the name of the Triangle, like ABC. Can be either None (the names will be automatically created), or a string of the letters to use to name the vertices. Only single letters are supported as Points' names so far (at Polygon's creation). See issue #3. :type name: None or str :param side_length: the length that will be used to calculate the coordinates of the vertices used to build the EquilateralTriangle :type side_length: a number :param mark_equal_sides: if True (default), all three sides will be automatically marked with the same symbol. :type mark_equal_sides: bool :param draw_vertices: whether to actually draw, or not, the vertices :type draw_vertices: bool :param label_vertices: whether to label, or not, the vertices :type label_vertices: bool :param thickness: the thickness of the Triangle's sides :type thickness: str :param color: the color of the Triangle's sides :type color: str :param rotate: the angle of rotation around isobarycenter :type rotate: int """ if start_vertex is None: start_vertex = Point(0, 0) self._side_length = Number(side_length) v1 = Point(side_length + start_vertex.x, start_vertex.y) v2 = Point(side_length / 2 + start_vertex.x, (side_length * Number(3).sqrt() * Number('0.5')).rounded( Number('0.001')) + start_vertex.y) Triangle.__init__(self, start_vertex, v1, v2, name=name, draw_vertices=draw_vertices, label_vertices=label_vertices, thickness=thickness, color=color, rotation_angle=rotation_angle, winding=winding, sloped_sides_labels=sloped_sides_labels) Equilateral.__init__(self, mark_equal_sides=mark_equal_sides, use_mark=use_mark) self._type = 'EquilateralTriangle'
def __init__(self, point, vertex, point_or_measure, decoration=None, mark_right=False, second_point_name='auto', label=None, color=None, thickness='thick', armspoints=None, label_vertex=False, draw_vertex=False, label_armspoints=False, draw_armspoints=False, label_endpoints=False, draw_endpoints=False, naming_mode='from_endpoints', decoration2=None): """ :param point: a Point of an arm of the Angle :type point: Point :param vertex: the Angle's vertex :type vertex: Point :param point_or_measure: either a Point of the other arm of the Angle, or the measure of the Angle :type point_or_measure: Point or number :param decoration: the decoration of the Angle :type decoration: None or AngleDecoration :param mark_right: to tell whether to mark the angle as a right angle :type mark_right: bool :param second_point_name: Only used if point_or_measure is a measure, this is the name of the 2d arm's Point. If set to 'auto', then the name of the first Point will be used, concatenated to a '. :type second_point_name: str :param thickness: the Angle's arms' thickness. Available values are TikZ's ones. :type thickness: str :param color: the color of the Angle's arms. :type color: str :param naming_mode: how to build the name. Possible modes are: 'from_endpoints', 'from_armspoints', 'from_vertex'. Note that if no armspoints are defined, then trying to get the Angle.name will raise an error :type naming_mode: str """ self.color = color self.thickness = thickness self.naming_mode = naming_mode self.decoration = decoration self.decoration2 = decoration2 # The label must be set *after* the possible decoration, because it # will actually be handled by self.decoration if (self.decoration is None or self.decoration.label in [None, 'default']): self.label = label else: if label is not None: raise ValueError('The label has been set twice, as Angle\'s ' 'keyword argument ({}) and as its ' 'AngleDecoration\'s keyword argument ({}).' .format(repr(label), repr(self.decoration.label_value))) self.mark_right = mark_right self.label_vertex = label_vertex self.label_endpoints = label_endpoints self.draw_endpoints = draw_endpoints self.label_armspoints = label_armspoints self.draw_armspoints = draw_armspoints self.draw_vertex = draw_vertex if not (isinstance(point, Point) and isinstance(vertex, Point) and (isinstance(point_or_measure, Point) or is_number(point_or_measure))): raise TypeError('Three Points, or two Points and the measure of ' 'the angle are required to build an Angle. ' 'Found instead: {}, {} and {}.' .format(type(point), type(vertex), type(point_or_measure))) self._points = [point, vertex] if isinstance(point_or_measure, Point): self._points.append(point_or_measure) else: self._points.append(point.rotate(vertex, point_or_measure, rename=second_point_name)) if any([p.three_dimensional for p in self._points]): self._three_dimensional = True else: self._three_dimensional = False # Measure of the angle: if self._three_dimensional: u = Vector(self.points[1], self.points[0]) v = Vector(self.points[1], self.points[2]) self._measure = Number(str(degrees(atan2(u.cross(v).length, u.dot(v))))) else: # 2D angles measure p0 = Point(self._points[0].x - self._points[1].x, self._points[0].y - self._points[1].y, None) p2 = Point(self._points[2].x - self._points[1].x, self._points[2].y - self._points[1].y, None) α0 = Number(str(degrees(atan2(p0.y, p0.x)))) α2 = Number(str(degrees(atan2(p2.y, p2.x)))) self._measure = α2 - α0 if self._measure < 0: self._measure += 360 # This is not like the matching Triangle! if shoelace_formula(*self.points) > 0: self.winding = 'clockwise' else: self.winding = 'anticlockwise' arm0 = Bipoint(self._points[1], self._points[0]) arm1 = Bipoint(self._points[1], self._points[2]) self._arms = [arm0, arm1] self.armspoints = armspoints # Only 2D: labels positioning if not self.three_dimensional: # Vertex' label positioning bisector = Vector(self._points[0], self.vertex)\ .bisector(Vector(self._points[2], self.vertex), new_endpoint_name=None) try: self._points[1].label_position = \ tikz_approx_position(bisector.slope360) except ZeroVector: self._points[1].label_position = \ tikz_approx_position( Bipoint(self.vertex, self._points[0].rotate(self.vertex, -90, rename=None) ).slope360) # Endpoints labels positioning direction = 1 if self.winding == 'anticlockwise' else -1 self.endpoints[0].label_position = \ tikz_approx_position(arm0.slope360 - direction * 55) self.endpoints[1].label_position = \ tikz_approx_position(arm1.slope360 + direction * 55)
def __init__(self, *points, start_vertex=None, name=None, width=Number(1), length=Number(2), mark_right_angles=True, draw_vertices=False, label_vertices=True, thickness='thick', color=None, rotation_angle=0, winding=None, sloped_sides_labels=True): r""" Initialize Rectangle. This will be done either using the points (if provided) of the points parameter or using the keyword arguments start_vertex, width... :param points: a list of 4 points that will be the vertices of the Rectangle. It is possible to provide no Point at all. :type points: a list of Points :param start_vertex: the vertex to start to draw the Rectangle (default (0; 0)) :type start_vertex: Point :param name: the name of the Rectangle, like ABCD. Can be either None (the names will be automatically created), or a string of the letters to use to name the vertices. Only single letters are supported as Points' names so far (at Polygon's creation). See issue #3. :type name: None or str :param width: the width that will be used to calculate the coordinates of the vertices used to build the Rectangle :type width: a number :param length: the length that will be used to calculate the coordinates of the vertices used to build the Rectangle :type length: a number :param mark_right_angles: if True (default), all four angles will be automatically marked as right angles. :type mark_right_angles: bool :param draw_vertices: whether to actually draw, or not, the vertices :type draw_vertices: bool :param label_vertices: whether to label, or not, the vertices :type label_vertices: bool :param thickness: the thickness of the Polygon's sides :type thickness: str :param color: the color of the Polygon's sides :type color: str :param rotate: the angle of rotation around isobarycenter :type rotate: int """ if points: (v0, v1, v2, v3) = points self._length = Bipoint(v0, v1).length self._width = Bipoint(v1, v2).length else: # 2D Rectangles only (so far) if start_vertex is None: start_vertex = Point(0, 0) # Accepted type for width and length is number, will be checked at # vertices' instanciations. self._width = width self._length = length v0 = start_vertex v1 = Point(length + start_vertex.x, start_vertex.y) v2 = Point(length + start_vertex.x, width + start_vertex.y) v3 = Point(start_vertex.x, width + start_vertex.y) Quadrilateral.__init__(self, v0, v1, v2, v3, name=name, draw_vertices=draw_vertices, label_vertices=label_vertices, thickness=thickness, color=color, rotation_angle=rotation_angle, winding=winding, sloped_sides_labels=sloped_sides_labels) self._type = 'Rectangle' if mark_right_angles: for a in self.angles: a.decoration = AngleDecoration(thickness=thickness) a.mark_right = True