Esempio n. 1
0
 def __init__(self, *args, allow_zero_length=True):
     """
     It's possible to create a Vector giving:
     - a Bipoint: Bipoint(A, B)
     - a pair of Points: A, B
     - its coordinates x, y or x, y, z
     """
     if not args or len(args) >= 4:
         raise TypeError('Vector() takes one, two or three arguments '
                         '({} given)'.format(len(args)))
     if len(args) == 1:
         from mathmakerlib.geometry.bipoint import Bipoint
         if not isinstance(args[0], Bipoint):
             raise TypeError('a Vector can be created from one Bipoint, '
                             'found {} instead.'.format(repr(args[0])))
         self._x = args[0].Δx
         self._y = args[0].Δy
         if args[0].three_dimensional:
             self._z = args[0].Δz
             self._three_dimensional = True
         else:
             self._z = Number(0)
             self._three_dimensional = False
     elif len(args) == 2:
         # Two Points
         if isinstance(args[0], Point) and isinstance(args[1], Point):
             self._x = args[1].x - args[0].x
             self._y = args[1].y - args[0].y
             if args[0].three_dimensional or args[1].three_dimensional:
                 self._three_dimensional = True
                 self._z = args[1].z - args[0].z
             else:
                 self._three_dimensional = False
                 self._z = Number(0)
         # Two numbers
         elif is_number(args[0]) and is_number(args[1]):
             self._three_dimensional = False
             self._x = Number(args[0])
             self._y = Number(args[1])
             self._z = Number(0)
         else:
             raise TypeError('a Vector can be created from two arguments, '
                             'either two Points or two numbers. '
                             'Found {} and {} instead.'.format(
                                 repr(args[0]), repr(args[1])))
     elif len(args) == 3:
         self._three_dimensional = True
         self._x = Number(args[0])
         self._y = Number(args[1])
         self._z = Number(args[2])
     self._length = Number(self.x**2 + self.y**2 + self.z**2).sqrt()
     if not allow_zero_length and self.length == 0:
         msg = 'Explicitly disallowed creation of a zero-length {}.'\
             .format(type(self).__name__)
         raise ZERO_OBJECTS_ERRORS[type(self).__name__](msg)
Esempio n. 2
0
def move_fracdigits_to(n, from_nb=None):
    """
    Turn n into decimal instead of all decimals found in the from_nb list.

    Each decimal found in the numbers' list will be recursively replaced by
    10 times itself (until it is no decimal anymore) while in the same time
    n will be divided by 10.

    This is useful for the case division by a decimal is unwanted.

    :param n: the number who will be divided by 10 instead of the others
    :type n: any number (int, Decimal, float though they're not advised)
    :param from_nb: an iterable containing the numbers that must be integers
    :type from_nb: a list (of numbers)
    :rtype: a list (of numbers)
    """
    if type(from_nb) is not list:
        raise TypeError('A list of numbers must be given as argument '
                        '\'numbers\'.')
    if not is_number(n):
        raise TypeError('The first argument must be a number.')
    n = Decimal(str(n))
    # If any element of from_nb is not a number, is_integer() will raise
    # an exception.
    if all([is_integer(i) for i in from_nb]):
        return [
            n,
        ] + [i for i in from_nb]
    numbers_copy = copy.deepcopy(from_nb)
    for i, j in enumerate(from_nb):
        if not is_integer(j):
            numbers_copy[i] = j * 10
            return move_fracdigits_to(n / 10, from_nb=numbers_copy)
Esempio n. 3
0
    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))
        ]
Esempio n. 4
0
 def radius(self, value):
     from mathmakerlib.calculus.number import Number
     if is_number(value):
         self._radius = Number(value)
     else:
         raise TypeError(
             'Expected a number as radius. Got {} instead.'.format(
                 str(type(value))))
Esempio n. 5
0
 def __new__(cls,
             sign=None,
             numerator=None,
             denominator=None,
             from_decimal=None):
     """
     Fraction(5, 8) is equivalent to Fraction('+', 5, 8).
     """
     if from_decimal is not None:
         f = Number(10)**max(1, from_decimal.fracdigits_nb())
         sign = Sign(from_decimal)
         numerator = (f * from_decimal).standardized()
         denominator = f.standardized()
     elif sign in ['+', '-']:
         if not (is_number(numerator) and is_number(denominator)):
             raise TypeError('Numerator and denominator must be numbers. '
                             'Got {} and {} instead.'.format(
                                 type(numerator), type(denominator)))
         if not (is_integer(numerator) and is_integer(denominator)):
             raise TypeError('Numerator and denominator must be integers. '
                             'Got {} and {} instead.'.format(
                                 numerator, denominator))
     else:
         if not (is_number(sign) and is_number(numerator)):
             raise TypeError('Numerator and denominator must be numbers. '
                             'Got {} and {} instead.'.format(
                                 type(sign), type(numerator)))
         elif not (is_integer(sign) and is_integer(numerator)):
             raise TypeError('Numerator and denominator must be integers. '
                             'Got {} and {} instead.'.format(
                                 sign, numerator))
         denominator = numerator
         numerator = sign
         sign = '+'
     # some initialization
     self = object.__new__(cls)
     self._sign = Sign(sign)
     self._numerator = Number(numerator)
     self._denominator = Number(denominator)
     return self
Esempio n. 6
0
 def reduced_by(self, n):
     """Return Fraction reduced by n (possibly return a Number)."""
     if not is_number(n):
         raise TypeError('A Fraction can be reduced only by an integer, '
                         'got {} instead.'.format(type(n)))
     if not is_integer(n):
         raise TypeError('A Fraction can be reduced only by an integer, '
                         'got {} instead.'.format(n))
     if not is_integer(self.numerator / n):
         raise ValueError(
             'Cannot divide {} by {} and get an integer.'.format(
                 self.numerator, n))
     if not is_integer(self.denominator / n):
         raise ValueError(
             'Cannot divide {} by {} and get an integer.'.format(
                 self.denominator, n))
     n = abs(n)
     if self.denominator / n == 1:
         return self.sign * self.numerator / n
     if self.denominator / n == -1:
         return Sign('-') * self.sign * self.numerator / n
     return Fraction(self.sign, self.numerator / n, self.denominator / n)
Esempio n. 7
0
    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)
Esempio n. 8
0
    def rotate(self, center, angle, axis=None, rename='auto'):
        """
        Rotate around center (or axis going through center).

        :param center: the center of the rotation
        :type center: Point
        :param angle: the angle of the rotation
        :type angle: a number
        :param axis: the axis of the rotation for 3D rotation. If left to None,
        the rotation happens around the center, in the plane.
        :type axis: Vector
        :param rename: if set to 'auto', will name the rotated Point after the
        original, adding a ' (like A.rotate(...) creates a Point A'). If set
        to None, keep the original name. Otherwise, the provided str will be
        used as the rotated Point's name.
        :type rename: None or str
        :rtype: Point
        """
        from mathmakerlib.geometry.vector import Vector
        if not isinstance(center, Point):
            raise TypeError('Expected a Point as rotation center, got {} '
                            'instead.'.format(type(center)))
        if not is_number(angle):
            raise TypeError('Expected a number as rotation angle, got {} '
                            'instead.'.format(type(angle)))
        if not (axis is None or isinstance(axis, Vector)):
            raise TypeError('Expected either None or a Vector as axis, '
                            'found {} instead.'.format(repr(axis)))
        Δx = self.x - center.x
        Δy = self.y - center.y
        Δz = self.z - center.z
        cosθ = Number(str(cos(radians(angle))))
        sinθ = Number(str(sin(radians(angle))))
        if axis is None:
            rx = (Δx * cosθ - Δy * sinθ + center.x).rounded(Number('1.000'))
            ry = (Δx * sinθ + Δy * cosθ + center.y).rounded(Number('1.000'))
            rz = 'undefined'
        else:
            ux, uy, uz = axis.normalized().coordinates
            rotation_matrix = [[
                cosθ + (1 - cosθ) * ux**2, ux * uy * (1 - cosθ) - uz * sinθ,
                ux * uz * (1 - cosθ) + uy * sinθ
            ],
                               [
                                   uy * ux * (1 - cosθ) + uz * sinθ,
                                   cosθ + (1 - cosθ) * uy**2,
                                   uy * uz * (1 - cosθ) - ux * sinθ
                               ],
                               [
                                   uz * ux * (1 - cosθ) - uy * sinθ,
                                   uz * uy * (1 - cosθ) + ux * sinθ,
                                   cosθ + (1 - cosθ) * uz**2
                               ]]
            rx = sum([
                rc * coord
                for rc, coord in zip(rotation_matrix[0], [Δx, Δy, Δz])
            ])
            ry = sum([
                rc * coord
                for rc, coord in zip(rotation_matrix[1], [Δx, Δy, Δz])
            ])
            rz = sum([
                rc * coord
                for rc, coord in zip(rotation_matrix[2], [Δx, Δy, Δz])
            ])
            rx += center.x
            ry += center.y
            rz += center.z
            rx = rx.rounded(Number('1.000'))
            ry = ry.rounded(Number('1.000'))
            rz = rz.rounded(Number('1.000'))

        if rename == 'keep_name':
            rname = self.name
        elif rename == 'auto':
            rname = self.name + "'"
        else:
            rname = rename
        return Point(rx, ry, rz, rname)
Esempio n. 9
0
    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,
                 object3D=None,
                 k=None,
                 α=None,
                 direction=None,
                 thickness='thick',
                 color=None,
                 draw_vertices=False,
                 label_vertices=False):
        r"""
        Initialize ObliqueProjection.

        :param object3D: the object to project.
        :type object3D: Polyhedron
        :param k: the ratio of the oblique projection. Defaults to
        config.oblique_projection.RATIO
        :type k: number
        :param α: the angle between the receding Z-axis, and X-axis.
        :type α: number
        :param direction: 'top-left', 'top-right', 'bottom-left' or
        'bottom-right'
        :type direction: str
        """
        self.draw_vertices = draw_vertices
        self.label_vertices = label_vertices
        if k is None:
            k = config.oblique_projection.RATIO
        if not is_number(k):
            raise TypeError(
                'Ratio k must be a number. Found {} instead.'.format(repr(k)))
        if α is None:
            α = config.oblique_projection.RECEDING_AXIS_ANGLE
        if direction is None:
            direction = config.oblique_projection.DIRECTION
        if not is_number(α):
            raise TypeError(
                'Angle α must be a number. Found {} instead.'.format(repr(α)))
        if not isinstance(object3D, Polyhedron):
            raise TypeError(
                'object3D must be a Polyhedron, found {} instead.'.format(
                    repr(object3D)))
        check_direction(direction)
        self._direction = direction
        self._object3D_name = type(object3D).__name__

        # Setup the edges' labels
        if object3D.labels is not None:
            edges_to_label = object3D.edges_to_label[
                'oblique_projection:{}'.format(direction)]
            w_coord, d_coord, h_coord = edges_to_label
            object3D.faces[w_coord[0]].sides[w_coord[1]].unlock_label()
            object3D.faces[w_coord[0]].sides[w_coord[1]].label_winding = \
                w_coord[2]
            object3D.faces[w_coord[0]].sides[w_coord[1]].label = \
                object3D.labels[0]
            object3D.faces[w_coord[0]].sides[w_coord[1]].lock_label()
            object3D.faces[d_coord[0]].sides[d_coord[1]].unlock_label()
            object3D.faces[d_coord[0]].sides[d_coord[1]].label_winding = \
                d_coord[2]
            object3D.faces[d_coord[0]].sides[d_coord[1]].label = \
                object3D.labels[1]
            object3D.faces[d_coord[0]].sides[d_coord[1]].lock_label()
            object3D.faces[h_coord[0]].sides[h_coord[1]].unlock_label()
            object3D.faces[h_coord[0]].sides[h_coord[1]].label_winding = \
                h_coord[2]
            object3D.faces[h_coord[0]].sides[h_coord[1]].label = \
                object3D.labels[2]
            object3D.faces[h_coord[0]].sides[h_coord[1]].lock_label()

        matrix = {
            'top-right': [[1, 0, k * sin(radians(α))],
                          [0, 1, k * cos(radians(α))]],
            'bottom-right': [[1, 0, k * sin(radians(α))],
                             [0, 1, -k * cos(radians(α))]],
            'bottom-left': [[1, 0, -k * sin(radians(α))],
                            [0, 1, -k * cos(radians(α))]],
            'top-left': [[1, 0, -k * sin(radians(α))],
                         [0, 1, k * cos(radians(α))]]
        }[direction]
        self._edges = []
        self._edges3D = {}
        self._vertices = []
        self._vertices_match = {}

        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)

        for vertex in object3D.vertices:
            projected_point = project(vertex, matrix)
            self._vertices.append(projected_point)
            self._vertices_match[vertex.name] = (vertex, projected_point)

        # To store to which edges a vertex belongs
        vertices_connexions = {k: [] for k in self._vertices}

        # Build the projected edges
        for edge in object3D.edges:
            p0 = self._vertices_match[edge.endpoints[0].name][1]
            p1 = self._vertices_match[edge.endpoints[1].name][1]
            # TODO: check cases when the projected edge is a single point
            # (ZeroBipoint should be raised)
            projected_edge = LineSegment(p0,
                                         p1,
                                         thickness=thickness,
                                         draw_endpoints=draw_vertices,
                                         label_endpoints=label_vertices,
                                         color=color,
                                         allow_zero_length=False,
                                         locked_label=True,
                                         label_scale=edge.label_scale,
                                         label=edge.label,
                                         label_mask=edge.label_mask,
                                         label_winding=edge.label_winding,
                                         sloped_label=False)
            vertices_connexions[p0].append(LineSegment(p0, p1))
            vertices_connexions[p1].append(LineSegment(p1, p0))
            if projected_edge not in self._edges:  # TODO: else, what...?
                self._edges.append(projected_edge)
            if projected_edge not in self._edges3D:  # TODO: else, what...?
                self._edges3D[projected_edge] = edge
        # Find out which edges are hidden.
        # The ones that belong to convex hull of the projected vertices are
        # considered visible. By default, they will remain visible (i.e. keep
        # the default 'solid' dashpattern).
        points_cloud = set()  # to avoid duplicates
        for edge in self.edges:
            points_cloud.update(edge.endpoints)
        cvh = convex_hull(*points_cloud)
        for edge in self.edges:
            # Only edges not belonging to the convex hull may be hidden
            if not (edge.endpoints[0] in cvh and edge.endpoints[1] in cvh):
                m = self._edges3D[edge].midpoint()
                pm = project(m, matrix)  # projected midpoint
                # Check if the midpoint of the tested edge is behind (i.e.
                # deeper) than a face while being inside it
                for f in object3D.faces:
                    pface = [project(v, matrix) for v in f.vertices]
                    if (all([v.z <= m.z for v in f.vertices])
                            and pm not in convex_hull(pm, *pface)
                            and not any(m.belongs_to(s) for s in f.sides)):
                        edge.dashpattern = \
                            config.oblique_projection.DASHPATTERN

        # Setup the vertices' labels
        for vertex in self.vertices:
            edges = sorted(vertices_connexions[vertex],
                           key=lambda edge: Vector(edge).slope360)
            couples = [(edges[n], edges[(n + 1) % len(edges)])
                       for n, _ in enumerate(edges)]
            widest = max(couples,
                         key=lambda couple: Vector(couple[0]).angle_measure(
                             Vector(couple[1])))
            u = Vector(vertex, widest[0].endpoints[1])
            v = Vector(vertex, widest[1].endpoints[1])
            vertex.label_position = \
                tikz_approx_position(u.bisector(v).slope360)
Esempio n. 11
0
    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)
Esempio n. 12
0
def check_scale(value, source_name):
    if not is_number(value):
        raise TypeError(
            'The {}\'s scale must be a number.'.format(source_name))
 def n(self, n):
     if not (is_number(n) and is_integer(n) and n >= 1):
         raise TypeError('n must be an integer >= 1, got {} instead.'
                         .format(n))
     self._n = Number(n)