コード例 #1
0
def test_bisector():
    """Check bisector of two Vectors."""
    Ω = Point(0, 0)
    pointI = Point(1, 0)
    J = Point(0, 1)
    A = Point(1, 1)
    i = Vector(Ω, pointI)
    j = Vector(Ω, J)
    a = Vector(Ω, A)
    assert i.bisector(j) == a
    assert j.bisector(i) == -a
    k = Vector(Ω, Point(2, 0))
    assert k.bisector(j) == a
    with pytest.raises(TypeError) as excinfo:
        k.bisector('j')
    assert str(excinfo.value) == 'Can only create the bisector with another ' \
        'Vector. Found \'j\' instead.'
コード例 #2
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)))
コード例 #3
0
    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)