def tikz_rightangle_mark(self, winding='anticlockwise'): if self.decoration is None or not self.mark_right: return '' check_winding(winding) # Decimal numbers in TikZ must be written with a dot as decimal point. # As of now, there is no reliable way to temporarily change the # locale to 'C' (or 'en_US'), so here's a little patch that will # replace possibly other decimal points by a '.'. theta = str(Bipoint(self.vertex, self.points[0]) .slope.rounded(Number('0.01')).printed) if (locale.localeconv()['decimal_point'] != '.' and locale.localeconv()['decimal_point'] in theta): theta = theta.replace(locale.localeconv()['decimal_point'], '.') rt = 'cm={{cos({θ}), sin({θ}), -sin({θ}), cos({θ}), ({v})}}' \ .format(θ=theta, v=self.vertex.name) draw_options = tikz_options_list([self.decoration.thickness, self.decoration.color, rt]) if winding == 'anticlockwise': rightangle_shape = '({R}, 0) -- ({R}, {R}) -- (0, {R})'\ .format(R=self.decoration.radius.uiprinted) elif winding == 'clockwise': rightangle_shape = '({R}, 0) -- ({R}, -{R}) -- (0, -{R})'\ .format(R=self.decoration.radius.uiprinted) return '\draw{} {};'.format(draw_options, rightangle_shape)
def test_instanciation(): """Check Vector's instanciation.""" u = Vector(Bipoint(Point(0, 0), Point(2, 5))) assert not u.three_dimensional u = Vector(Bipoint(Point(0, 0, 0), Point(2, 5))) assert u.three_dimensional v = Vector(Point(0, 0), Point(2, 5)) assert not v.three_dimensional v = Vector(Point(0, 0, 0), Point(2, 5)) assert v.three_dimensional w = Vector(2, 5) assert not w.three_dimensional assert w.z == 0 assert w.x == 2 assert w.y == 5 assert w.coordinates == (2, 5, 0) w = Vector(2, 5, 0) assert w.three_dimensional
def test_cross_product(): """Check cross product of two Vectors.""" Ω = Point(0, 0, 'Ω') A = Point(1, 1, 'A') with pytest.raises(TypeError) as excinfo: Vector(0, 1).cross(Bipoint(Ω, A)) assert str(excinfo.value) == 'Can only calculate the cross product of a '\ 'Vector by another Vector. '\ 'Found Bipoint(Point Ω(0, 0), Point A(1, 1)) instead.' assert Vector(1, 0).cross(Vector(0, 1)) == Vector(0, 0, 1) assert Vector(1, 0, 0).cross(Vector(0, 1, 0)) == Vector(0, 0, 1) assert Vector(3, 4, 5).cross(Vector(2, 5, 6)) == Vector(-1, -8, 7)
def test_addition(): """Check Vectors' additions.""" Ω = Point(0, 0, 'Ω') A = Point(1, 1, 'A') with pytest.raises(TypeError) as excinfo: Vector(0, 1) + Bipoint(Ω, A) assert str(excinfo.value) == 'Can only add a Vector to another Vector. '\ 'Found Bipoint(Point Ω(0, 0), Point A(1, 1)) instead.' u = Vector(1, 1) v = Vector(3, -7) assert u + v == Vector(4, -6) v = Vector(3, -7, 2) assert u + v == Vector(4, -6, 2) u = Vector(2, 5, -7) assert u + v == Vector(5, -2, -5)
def test_dot_product(): """Check dot product of two Vectors.""" Ω = Point(0, 0, 'Ω') A = Point(1, 1, 'A') with pytest.raises(TypeError) as excinfo: Vector(0, 1).dot(Bipoint(Ω, A)) assert str(excinfo.value) == 'Can only calculate the dot product of a '\ 'Vector by another Vector. '\ 'Found Bipoint(Point Ω(0, 0), Point A(1, 1)) instead.' u = Vector(1, 1) v = Vector(3, -7) assert u.dot(v) == -4 u = Vector(2, 5, -7) v = Vector(3, -7, 2) assert u.dot(v) == -43
def test_equality(): """Check __eq__() is correct.""" assert Vector(1, 1) != Bipoint(Point(0, 0), Point(1, 1)) assert Vector(1, 1) == Vector(Point(1, 1), Point(2, 2))
def test_repr(): """Check Vector.__repr__()""" u = Vector(Bipoint(Point(0, 0), Point(2, 5))) assert repr(u) == 'Vector(2, 5)' u = Vector(Bipoint(Point(0, 0, 0), Point(2, 5))) assert repr(u) == 'Vector(2, 5, 0)'
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