def test_addi_hole():
    """Check all normal cases are correctly handled."""
    o = addi_hole.sub_object(build_data=[10, 2],
                             nb_source='complements_to_10')
    assert o.result == wrap_nb('10')
    o = addi_hole.sub_object(build_data=[21, 21])
    assert o.transduration == 16
    o = addi_hole.sub_object(build_data=[5, 7], hidden=1)
    assert '?' in o.nb1.printed
    o = addi_hole.sub_object(build_data=[5, 7], hidden=2)
    assert '?' in o.nb2.printed
    o = addi_hole.sub_object(build_data=[10, 20], nb_variant='decimal1')
    assert ((is_number(o.nb1) and not is_integer(o.nb1))
            or (is_number(o.nb2) and not is_integer(o.nb2))
            or (is_number(o.result_nb) and not is_integer(o.result_nb)))
Example #2
0
 def tikz_attributes(self, radius_coeff=1, do_label=True):
     if not is_number(radius_coeff):
         raise TypeError('radius_coeff must be a number, found {} instead.'
                         .format(type(radius_coeff)))
     attributes = []
     if do_label and self.label not in [None, 'default']:
         required.tikz_library['quotes'] = True
         attributes.append('"{}"'.format(self.label))
         if self.eccentricity is not None:
             attributes.append('angle eccentricity={}'
                               .format(self.eccentricity))
     if self.variety is not None:
         if self.do_draw:
             attributes.append('draw')
         if self.arrow_tips is not None:
             attributes.append(self.arrow_tips)
         if self.thickness is not None:
             attributes.append(self.thickness)
         if self.radius is not None:
             attributes.append('angle radius = {}'
                               .format((self.radius * radius_coeff)
                                       .rounded(Number('0.01')).uiprinted))
         if self.hatchmark is not None:
             attributes.append(self.hatchmark)
             required.tikz_library['decorations.markings'] = True
             required.tikzset[self.hatchmark + '_hatchmark'] = True
     if (self.variety is not None
         or (do_label and self.label not in [None, 'default'])):
         if self.color is not None:
             attributes.append(self.color)
     return '[{}]'.format(', '.join(attributes))
Example #3
0
 def gap(self, value):
     if not (is_number(value) or value is None):
         raise TypeError('The gap value must be None or a number. '
                         'Found {} instead (type: {}).'
                         .format(repr(value), type(value)))
     if value is None:
         self._gap = None
     else:
         self._gap = Number(value)
Example #4
0
 def radius(self, value):
     if is_number(value):
         self._radius = Number(value)
     elif value is None:
         self._radius = None
     else:
         raise TypeError('Expected a number as radius. Got {} instead.'
                         .format(str(type(value))))
     if hasattr(self, '_eccentricity') and hasattr(self, '_gap'):
         self.eccentricity = 'automatic'
Example #5
0
 def eccentricity(self, value):
     if value == 'automatic':
         if self.gap is None:
             raise ValueError('Cannot calculate the eccentricity if gap '
                              'is None.')
         value = (self.gap / self.radius + 1)\
             .rounded(Number('0.01')).standardized()
     if not (value is None or is_number(value)):
         raise TypeError('The eccentricity of an AngleDecoration must be '
                         'None or a Number. Found {} instead.'
                         .format(type(value)))
     self._eccentricity = value
Example #6
0
 def DEFAULT_POSITION_PRECISION(self, value):
     if not is_number(value):
         raise TypeError('DEFAULT_POSITION_PRECISION must be a number, '
                         'found {} instead.'.format(type(value)))
     self._DEFAULT_POSITION_PRECISION = value
Example #7
0
 def RATIO(self, value):
     if not is_number(value):
         raise TypeError('RATIO must be a number, found {} instead.'.format(
             repr(value)))
     self._RATIO = value
Example #8
0
 def RECEDING_AXIS_ANGLE(self, value):
     if not is_number(value):
         raise TypeError('RECEDING_AXIS_ANGLE must be a number, '
                         'found {} instead.'.format(repr(value)))
     self._RECEDING_AXIS_ANGLE = value
Example #9
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)