Ejemplo n.º 1
0
    def _append(self, point: Vec2, normal: Vec2, width: float) -> None:
        """
        Add a curve trace station (like a vertex) at location `point`.

        Args:
            point: 2D curve location (vertex), z-axis of 3D vertices is ignored.
            normal: curve normal
            width:  width of station

        """
        if _NULLVEC2.isclose(normal):
            normal = _NULLVEC2
        else:
            normal = normal.normalize(width / 2)
        self._stations.append(CurveStation(point + normal, point - normal))
Ejemplo n.º 2
0
    def __init__(self,
                 center: 'Vertex' = (0, 0),
                 radius: float = 1,
                 start_angle: float = 0,
                 end_angle: float = 360,
                 is_counter_clockwise: bool = True):

        self.center = Vec2(center)
        self.radius = radius
        if is_counter_clockwise:
            self.start_angle = start_angle
            self.end_angle = end_angle
        else:
            self.start_angle = end_angle
            self.end_angle = start_angle
Ejemplo n.º 3
0
    def apply_text_shift(self, location: Vec2, text_rotation: float) -> Vec2:
        """
        Add `self.text_shift_h` and `sel.text_shift_v` to point `location`, shifting along and perpendicular to
        text orientation defined by `text_rotation`

        Args:
            location: location point
            text_rotation: text rotation in degrees

        Returns: new location

        """
        shift_vec = Vec2((self.text_shift_h, self.text_shift_v))
        location += shift_vec.rotate(text_rotation)
        return location
Ejemplo n.º 4
0
 def render(self, layout: 'GenericLayoutType', dxfattribs: dict = None):
     dxfattribs['closed'] = True
     center = self.shape[0]
     d = Vec2((self.radius / 2, 0))
     p1 = center - d
     p2 = center + d
     if layout.dxfversion > 'AC1009':
         dxfattribs['const_width'] = self.radius
         layout.add_lwpolyline([(p1, 1), (p2, 1)], format='vb', dxfattribs=dxfattribs)
     else:
         dxfattribs['default_start_width'] = self.radius
         dxfattribs['default_end_width'] = self.radius
         polyline = layout.add_polyline2d(points=[p1, p2], dxfattribs=dxfattribs)
         polyline[0].dxf.bulge = 1
         polyline[1].dxf.bulge = 1
Ejemplo n.º 5
0
 def add_horiz_ext_line_default(self, start: "Vertex") -> None:
     """Add horizontal outside extension line from start for default
     locations.
     """
     attribs = self.dimension_line.dxfattribs()
     self.add_line(start, self.outside_default_defpoint, dxfattribs=attribs)
     if self.measurement.vertical_placement == 0:
         hdist = self.arrows.arrow_size
     else:
         hdist = self._total_text_width
     angle = self.dim_line_angle % 360.0  # normalize 0 .. 360
     if 90 < angle <= 270:
         hdist = -hdist
     end = self.outside_default_defpoint + Vec2((hdist, 0))
     self.add_line(self.outside_default_defpoint, end, dxfattribs=attribs)
 def test_intersecting_zig_zag_lines(self):
     pline1 = Vec2.list([(0, 0), (2, 2), (4, 0), (6, 2), (8, 0)])
     pline2 = Vec2.list([(0, 2), (2, 0), (4, 2), (6, 0), (8, 2)])
     res = intersect_polylines_2d(pline1, pline2)
     assert len(res) == 4
     res.sort()  # do not rely on any order
     assert res[0].isclose(Vec2(1, 1))
     assert res[1].isclose(Vec2(3, 1))
     assert res[2].isclose(Vec2(5, 1))
     assert res[3].isclose(Vec2(7, 1))
Ejemplo n.º 7
0
    def _better(self, pk1, pk2, center: float, tar_ang: float):
        """
        求解center至边线夹角更接近tar_ang的位置

        Args:
            pk1 (float): 桩号1
            pk2 (float): 桩号2
            center (float): 中心点桩号
            tar_ang: 弧度,目标夹角,左侧为逆时针小于pi,右侧大于pi

        Returns:

        """
        norm = Vec2(*self.get_direction(center))
        ccpt = Vec2(*self.get_coordinate(center))
        pkm = (pk1 + pk2) * 0.5
        wl1, wr1 = self.get_width(pk1)
        wlm, wrm = self.get_width(pkm)
        wl2, wr2 = self.get_width(pk2)
        if tar_ang <= pi:  # 左侧目标角度
            find_dir = 0.5 * pi
            w1 = wl1
            wm = wlm
            w2 = wl2
        else:
            find_dir = -0.5 * pi
            w1 = wr1
            wm = wrm
            w2 = wr2

        pt1 = Vec2(*self.get_coordinate(pk1)) + Vec2(*self.get_direction(pk1)).rotate(find_dir) * w1
        ptm = Vec2(*self.get_coordinate(pkm)) + Vec2(*self.get_direction(pkm)).rotate(find_dir) * wm
        pt2 = Vec2(*self.get_coordinate(pk2)) + Vec2(*self.get_direction(pk2)).rotate(find_dir) * w2

        val1 = signed_angle_between(norm, pt1 - ccpt)
        valm = signed_angle_between(norm, ptm - ccpt)
        val2 = signed_angle_between(norm, pt2 - ccpt)
        if (val1 - tar_ang) * (val2 - tar_ang) < 0:
            if (val1 - tar_ang) * (valm - tar_ang) < 0:
                return pk1, pkm
            else:
                return pkm, pk2
        else:
            return None
def test_tangents():
    angles = [0, 45, 90, 135, -45, -90, -135, 180]
    sin45 = math.sin(math.pi / 4)
    result = [
        (0, 1),
        (-sin45, sin45),
        (-1, 0),
        (-sin45, -sin45),
        (sin45, sin45),
        (1, 0),
        (sin45, -sin45),
        (0, -1),
    ]
    arc = ConstructionArc(center=(1, 1))
    vertices = list(arc.tangents(angles))
    for v, r in zip(vertices, result):
        assert v.isclose(Vec2(r))
Ejemplo n.º 9
0
 def __init__(self, insert: Vertex, size: float = 1.0, angle: float = 0):
     # shape = [lower_left, lower_right, upper_right, upper_left, connection point]
     s2 = size / 2
     super().__init__([
         Vec2((-s2, -s2)),
         Vec2((+s2, -s2)),
         Vec2((+s2, +s2)),
         Vec2((-s2, +s2)),
         Vec2((-s2, 0)),
         Vec2((-size, 0)),
     ])
     self.place(insert, angle)
Ejemplo n.º 10
0
def test_two_angled_faces():
    t = LinearTrace()
    t.add_station((0, 0), 1, 0.5)
    t.add_station((2, 0), 1, 1)
    t.add_station((4, 2), 1, 1)
    face1, face2 = list(t.faces())
    assert face1[0].isclose(Vec2(0, +0.5))
    assert face1[1].isclose(Vec2(0, -0.5))
    assert face1[2].isclose(Vec2(2.5224077499274835, -0.18469903125906456))
    assert face1[3].isclose(Vec2(1.5936828611675133, 0.3007896423540608))
    assert face2[2].isclose(Vec2(4.353553390593274, 1.6464466094067263))
    assert face2[3].isclose(Vec2(3.646446609406726, 2.353553390593274))
Ejemplo n.º 11
0
    def default_text_location(self) -> Vec2:
        """Calculate default text location in UCS based on `self.text_halign`,
        `self.text_valign` and `self.text_outside`

        """
        start = self.dim_line_start
        end = self.dim_line_end
        measurement = self.measurement
        halign = measurement.text_halign
        # positions the text above and aligned with the first/second extension line
        ext_lines = self.extension_lines
        if halign in (3, 4):
            # horizontal location
            hdist = measurement.text_gap + measurement.text_height / 2.0
            hvec = self.dim_line_vec * hdist
            location = (start if halign == 3 else end) - hvec
            # vertical location
            vdist = ext_lines.extension_above + self._total_text_width / 2.0
            location += Vec2.from_deg_angle(
                self.ext_line_angle).normalize(vdist)
        else:
            # relocate outside text to center location
            if measurement.text_is_outside:
                halign = 0

            if halign == 0:
                location = self.dim_line_center  # center of dimension line
            else:
                hdist = (self._total_text_width / 2.0 +
                         self.arrows.arrow_size + measurement.text_gap)
                if (halign == 1
                    ):  # positions the text next to the first extension line
                    location = start + (self.dim_line_vec * hdist)
                else:  # positions the text next to the second extension line
                    location = end - (self.dim_line_vec * hdist)

            if measurement.text_is_outside:  # move text up
                vdist = (ext_lines.extension_above + measurement.text_gap +
                         measurement.text_height / 2.0)
            else:
                # distance from extension line to text midpoint
                vdist = measurement.text_vertical_distance()
            location += self.dim_line_vec.orthogonal().normalize(vdist)

        return location
Ejemplo n.º 12
0
    def add_arc(
        self,
        center: "Vertex",
        radius: float = 1.0,
        start_angle: float = 0.0,
        end_angle: float = 360.0,
        ccw: bool = True,
    ) -> "ArcEdge":
        """Add an :class:`ArcEdge`.

        **Adding Clockwise Oriented Arcs:**

        Clockwise oriented :class:`ArcEdge` objects are sometimes necessary to
        build closed loops, but the :class:`ArcEdge` objects are always
        represented in counter-clockwise orientation.
        To add a clockwise oriented :class:`ArcEdge` you have to swap the
        start- and end angle and set the `ccw` flag to ``False``,
        e.g. to add a clockwise oriented :class:`ArcEdge` from 180 to 90 degree,
        add the :class:`ArcEdge` in counter-clockwise orientation with swapped
        angles::

            edge_path.add_arc(center, radius, start_angle=90, end_angle=180, ccw=False)

        Args:
            center: center point of arc, (x, y)-tuple
            radius: radius of circle
            start_angle: start angle of arc in degrees (`end_angle` for a
                clockwise oriented arc)
            end_angle: end angle of arc in degrees (`start_angle` for a
                clockwise oriented arc)
            ccw: ``True`` for counter clockwise ``False`` for
                clockwise orientation

        """
        arc = ArcEdge()
        arc.center = Vec2(center)
        arc.radius = radius
        # Start- and end angles always for counter-clockwise oriented arcs!
        arc.start_angle = start_angle
        arc.end_angle = end_angle
        # Flag to export the counter-clockwise oriented arc in
        # correct clockwise orientation:
        arc.ccw = bool(ccw)
        self.edges.append(arc)
        return arc
Ejemplo n.º 13
0
    def add_text(self,
                 text: str,
                 pos: Vector,
                 rotation: float,
                 dxfattribs: dict = None) -> None:
        """
        Add TEXT (DXF R12) or MTEXT (DXF R2000+) entity to the dimension BLOCK.

        Args:
            text: text as string
            pos: insertion location in UCS
            rotation: rotation angle in degrees in UCS (x-axis is 0 degrees)
            dxfattribs: additional or overridden DXF attributes

        """
        attribs = self.default_attributes()
        attribs['style'] = self.text_style_name
        attribs['color'] = self.text_color
        if self.requires_extrusion:
            attribs['extrusion'] = self.ucs.uz

        if self.supports_dxf_r2000:
            text_direction = self.ucs.to_wcs(
                Vec2.from_deg_angle(rotation)) - self.ucs.origin
            attribs['text_direction'] = text_direction
            attribs['char_height'] = self.text_height
            attribs['insert'] = self.wcs(pos)
            attribs['attachment_point'] = self.text_attachment_point

            if self.supports_dxf_r2007:
                if self.text_fill:
                    attribs['box_fill_scale'] = self.text_box_fill_scale
                    attribs['bg_fill_color'] = self.text_fill_color
                    attribs['bg_fill'] = 3 if self.text_fill == 1 else 1

            if dxfattribs:
                attribs.update(dxfattribs)
            self.block.add_mtext(text, dxfattribs=attribs)
        else:
            attribs['rotation'] = self.ucs.to_ocs_angle_deg(rotation)
            attribs['height'] = self.text_height
            if dxfattribs:
                attribs.update(dxfattribs)
            dxftext = self.block.add_text(text, dxfattribs=attribs)
            dxftext.set_pos(self.ocs(pos), align='MIDDLE_CENTER')
Ejemplo n.º 14
0
 def test_two_single_paths(self):
     p1 = path.Path()
     p1.line_to((4, 5, 6))
     p2 = path.Path()
     p2.line_to((7, 8, 6))
     mpath = path.to_matplotlib_path([p1, p2])
     assert tuple(mpath.codes) == (
         MC.MOVETO,
         MC.LINETO,
         MC.MOVETO,
         MC.LINETO,
     )
     assert Vec2.list(mpath.vertices) == [
         (0, 0),
         (4, 5),
         (0, 0),
         (7, 8),
     ]
Ejemplo n.º 15
0
    def location_override(self,
                          location: 'Vertex',
                          leader=False,
                          relative=False) -> None:
        """
        Set user defined dimension text location. ezdxf defines a user defined location per definition as 'outside'.

        Args:
            location: text midpoint
            leader: use leader or not (movement rules)
            relative: is location absolute (in UCS) or relative to dimension line center.

        """
        self.dim_style.set_location(location, leader, relative)
        self.user_location = Vec2(location)
        self.text_movement_rule = 1 if leader else 2
        self.relative_user_location = relative
        self.text_outside = True
Ejemplo n.º 16
0
    def get_default_text_location(self) -> Vec2:
        """Returns default text midpoint based on `text_valign` and
        `text_outside`.
        """
        measurement = self.measurement
        if measurement.text_is_outside and measurement.text_outside_horizontal:
            return super().get_default_text_location()

        text_direction = Vec2.from_deg_angle(measurement.text_rotation)
        vertical_direction = text_direction.orthogonal(ccw=True)
        vertical_distance = measurement.text_vertical_distance()
        if measurement.text_is_inside:
            text_midpoint = self.center
        else:
            hdist = (self._total_text_width / 2.0 + self.arrows.arrow_size +
                     measurement.text_gap)
            text_midpoint = self.point_on_circle + (self.dim_line_vec * hdist)
        return text_midpoint + (vertical_direction * vertical_distance)
Ejemplo n.º 17
0
    def set_boundary_path(self, vertices: Iterable["Vertex"]) -> None:
        """Set boundary path to `vertices`. Two vertices describe a rectangle
        (lower left and upper right corner), more than two vertices is a polygon
        as clipping path.

        """
        _vertices = Vec2.list(vertices)
        if len(_vertices):
            if len(_vertices) > 2 and not _vertices[-1].isclose(_vertices[0]):
                # Close path, otherwise AutoCAD crashes
                _vertices.append(_vertices[0])
            self._boundary_path = _vertices
            self.set_flag_state(self.USE_CLIPPING_BOUNDARY, state=True)
            self.dxf.clipping = 1
            self.dxf.clipping_boundary_type = 1 if len(_vertices) < 3 else 2
            self.dxf.count_boundary_points = len(self._boundary_path)
        else:
            self.reset_boundary_path()
Ejemplo n.º 18
0
    def set_masking_area(self, vertices: Iterable["Vertex"]) -> None:
        """Set a new masking area, the area is placed in the layout xy-plane."""
        self.update_dxf_attribs(self.DEFAULT_ATTRIBS)
        vertices = Vec2.list(vertices)
        bounds = BoundingBox2d(vertices)
        x_size, y_size = bounds.size

        dxf = self.dxf
        dxf.insert = Vec3(bounds.extmin)
        dxf.u_pixel = Vec3(x_size, 0, 0)
        dxf.v_pixel = Vec3(0, y_size, 0)

        def boundary_path():
            extmin = bounds.extmin
            for vertex in vertices:
                v = vertex - extmin
                yield Vec2(v.x / x_size - 0.5, 0.5 - v.y / y_size)

        self.set_boundary_path(boundary_path())
Ejemplo n.º 19
0
def test_closed_linear_path():
    t = LinearTrace()
    t.add_station((0, 0), 1, 1)
    t.add_station((1, 0), 1, 1)
    t.add_station((1, 1), 1, 1)
    t.add_station((0, 1), 1, 1)
    t.add_station((0, 0), 1, 1)
    faces = list(t.faces())
    assert len(faces) == 4
    assert faces[0] == (
        Vec2(0.5, 0.5),
        Vec2(-0.5, -0.5),
        Vec2(1.5, -0.5),
        Vec2(0.5, 0.5),
    )
    assert faces[3] == (
        Vec2(0.5, 0.5),
        Vec2(-0.5, 1.5),
        Vec2(-0.5, -0.5),
        Vec2(0.5, 0.5),
    )
Ejemplo n.º 20
0
    def test_one_multi_path(self):
        p = path.Path()
        p.line_to((4, 5, 6))
        p.move_to((0, 0, 0))
        p.line_to((7, 8, 9))

        mpath = path.to_matplotlib_path([p])
        assert tuple(mpath.codes) == (
            MC.MOVETO,
            MC.LINETO,
            MC.MOVETO,
            MC.LINETO,
        )
        assert Vec2.list(mpath.vertices) == [
            (0, 0),
            (4, 5),
            (0, 0),
            (7, 8),
        ]
Ejemplo n.º 21
0
 def is_inside(self, point: "Vertex") -> bool:
     """Returns ``True`` if `point` is inside of box."""
     point = Vec2(point)
     delta = self.center - point
     if abs(self.angle) < ABS_TOL:  # fast path for horizontal rectangles
         return abs(delta.x) <= (self._width / 2.0) and abs(
             delta.y) <= (self._height / 2.0)
     else:
         distance = delta.magnitude
         if distance > self.circumcircle_radius:
             return False
         elif distance <= self.incircle_radius:
             return True
         else:
             # inside if point is "left of line" of all border lines.
             p1, p2, p3, p4 = self.corners
             return all(
                 (point_to_line_relation(point, a, b) < 1
                  for a, b in [(p1, p2), (p2, p3), (p3, p4), (p4, p1)]))
Ejemplo n.º 22
0
 def build_edge_path(hatch: Hatch, path: Path, flags: int):
     if path.has_curves:  # Edge path with LINE and SPLINE edges
         edge_path = hatch.paths.add_edge_path(flags)
         for edge in to_bsplines_and_vertices(path, g1_tol=g1_tol):
             if isinstance(edge, BSpline):
                 edge_path.add_spline(
                     control_points=edge.control_points,
                     degree=edge.degree,
                     knot_values=edge.knots(),
                 )
             else:  # add LINE edges
                 prev = edge[0]
                 for p in edge[1:]:
                     edge_path.add_line(prev, p)
                     prev = p
     else:  # Polyline boundary path
         hatch.paths.add_polyline_path(Vec2.generate(
             path.flattening(distance, segments)),
                                       flags=flags)
def measure_fixed_angle(msp, angle: float):
    x_dist = 15
    radius = 3
    distance = 1
    delta = angle / 2.0
    for dimtad, y_dist in [[0, 0], [1, 20], [4, 40]]:
        for count in range(8):
            center = Vec2(x_dist * count, y_dist)
            main_angle = 45.0 * count
            start_angle = main_angle - delta
            end_angle = main_angle + delta
            yield msp.add_angular_dim_cra(
                center,
                radius,
                start_angle,
                end_angle,
                distance,
                override={"dimtad": dimtad},
            )
def test_dimension_line_divided_by_measurement_text(doc: Drawing, s, e):
    """Vertical centered measurement text should hide the part of the
    dimension line beneath the text. This creates two arcs instead of one.
    """
    msp = doc.modelspace()
    dim = msp.add_angular_dim_cra(
        center=Vec2(),
        radius=5,
        start_angle=s,
        end_angle=e,
        distance=2,
        override={"dimtad": 0},  # vertical centered text
    )
    dim.render()
    arcs = dim.dimension.get_geometry_block().query("ARC")
    assert len(arcs) == 2
    assert sum(
        arc_angle_span_deg(arc.dxf.start_angle, arc.dxf.end_angle)
        for arc in arcs) < arc_angle_span_deg(
            s, e), "sum of visual arcs should be smaller than the full arc"
Ejemplo n.º 25
0
        def to_spline_edge(e: EllipseEdge) -> SplineEdge:
            # No OCS transformation needed, source ellipse and target spline
            # reside in the same OCS.
            ellipse = ConstructionEllipse(
                center=e.center,
                major_axis=e.major_axis,
                ratio=e.ratio,
                start_param=e.start_param,
                end_param=e.end_param,
            )
            count = max(int(float(num) * ellipse.param_span / math.tau), 3)
            tool = BSpline.ellipse_approximation(ellipse, count)
            spline = SplineEdge()
            spline.degree = tool.degree
            if not e.ccw:
                tool = tool.reverse()

            spline.control_points = Vec2.list(tool.control_points)
            spline.knot_values = tool.knots()  # type: ignore
            spline.weights = tool.weights()  # type: ignore
            return spline
Ejemplo n.º 26
0
    def add_dimension_line(self, start: Vec2, end: Vec2) -> None:
        """Add dimension line to dimension BLOCK, adds extension DIMDLE if
        required, and uses DIMSD1 or DIMSD2 to suppress first or second part of
        dimension line. Removes line parts hidden by dimension text.

        Args:
            start: dimension line start
            end: dimension line end

        """
        dim_line = self.dimension_line
        arrows = self.arrows
        extension = self.dim_line_vec * dim_line.extension
        ticks = arrows.has_ticks
        if ticks or ARROWS.has_extension_line(arrows.arrow1_name):
            start = start - extension
        if ticks or ARROWS.has_extension_line(arrows.arrow2_name):
            end = end + extension

        attribs = dim_line.dxfattribs()

        if dim_line.suppress1 or dim_line.suppress2:
            # TODO: results not as expected, but good enough
            # center should take into account text location
            center = start.lerp(end)
            if not dim_line.suppress1:
                self.add_line(start,
                              center,
                              dxfattribs=attribs,
                              remove_hidden_lines=True)
            if not dim_line.suppress2:
                self.add_line(center,
                              end,
                              dxfattribs=attribs,
                              remove_hidden_lines=True)
        else:
            self.add_line(start,
                          end,
                          dxfattribs=attribs,
                          remove_hidden_lines=True)
Ejemplo n.º 27
0
        def _edges(points) -> Iterable[Union[LineEdge, ArcEdge]]:
            prev_point = None
            prev_bulge = None
            for x, y, bulge in points:
                point = Vec3(x, y)
                if prev_point is None:
                    prev_point = point
                    prev_bulge = bulge
                    continue

                if prev_bulge != 0:
                    arc = ArcEdge()
                    # bulge_to_arc returns always counter-clockwise oriented
                    # start- and end angles:
                    (
                        arc.center,
                        start_angle,
                        end_angle,
                        arc.radius,
                    ) = bulge_to_arc(prev_point, point, prev_bulge)
                    chk_point = arc.center + Vec2.from_angle(
                        start_angle, arc.radius
                    )
                    arc.ccw = chk_point.isclose(prev_point, abs_tol=1e-9)
                    arc.start_angle = math.degrees(start_angle) % 360.0
                    arc.end_angle = math.degrees(end_angle) % 360.0
                    if math.isclose(
                        arc.start_angle, arc.end_angle
                    ) and math.isclose(arc.start_angle, 0):
                        arc.end_angle = 360.0
                    yield arc
                else:
                    line = LineEdge()
                    line.start = (prev_point.x, prev_point.y)
                    line.end = (point.x, point.y)
                    yield line

                prev_point = point
                prev_bulge = bulge
Ejemplo n.º 28
0
 def render(self, layout: "GenericLayoutType", dxfattribs: dict = None):
     center = self.shape[0]
     d = Vec2((self.radius / 2, 0))
     p1 = center - d
     p2 = center + d
     dxfattribs = dxfattribs or {}
     if layout.dxfversion > "AC1009":
         dxfattribs["const_width"] = self.radius
         layout.add_lwpolyline(
             [(p1, 1), (p2, 1)],
             format="vb",
             close=True,
             dxfattribs=dxfattribs,
         )
     else:
         dxfattribs["default_start_width"] = self.radius
         dxfattribs["default_end_width"] = self.radius
         polyline = layout.add_polyline2d(points=[p1, p2],
                                          close=True,
                                          dxfattribs=dxfattribs)
         polyline[0].dxf.bulge = 1
         polyline[1].dxf.bulge = 1
Ejemplo n.º 29
0
def convex_hull_2d(points: Iterable['Vertex']) -> List['Vertex']:
    """ Returns 2D convex hull for `points`.

    Args:
        points: iterable of points as :class:`Vec3` compatible objects,
            z-axis is ignored

    """

    def _convexhull(hull):
        while len(hull) > 2:
            # the last three points
            start_point, check_point, destination_point = hull[-3:]
            # curve not turns right
            if not is_point_left_of_line(check_point, start_point,
                                         destination_point):
                # remove the penultimate point
                del hull[-2]
            else:
                break
        return hull

    points = sorted(set(Vec2.generate(points)))  # remove duplicate points

    if len(points) < 3:
        raise ValueError(
            "Convex hull calculation requires 3 or more unique points.")

    upper_hull = points[:2]  # first two points
    for next_point in points[2:]:
        upper_hull.append(next_point)
        upper_hull = _convexhull(upper_hull)
    lower_hull = [points[-1], points[-2]]  # last two points

    for next_point in reversed(points[:-2]):
        lower_hull.append(next_point)
        lower_hull = _convexhull(lower_hull)
    upper_hull.extend(lower_hull[1:-1])
    return upper_hull
Ejemplo n.º 30
0
    def intersect(self, other: "ConstructionRay") -> Vec2:
        """Returns the intersection point as ``(x, y)`` tuple of `self` and
        `other`.

        Raises:
             ParallelRaysError: if rays are parallel

        """
        ray1 = self
        ray2 = other
        if ray1.is_parallel(ray2):
            raise ParallelRaysError("Rays are parallel")

        if ray1._is_vertical:
            x = ray1._location.x
            if ray2.is_horizontal:
                y = ray2._location.y
            else:
                y = ray2.yof(x)
        elif ray2._is_vertical:
            x = ray2._location.x
            if ray1.is_horizontal:
                y = ray1._location.y
            else:
                y = ray1.yof(x)
        elif ray1._is_horizontal:
            y = ray1._location.y
            x = ray2.xof(y)
        elif ray2._is_horizontal:
            y = ray2._location.y
            x = ray1.xof(y)
        else:
            # calc intersection with the 'straight-line-equation'
            # based on y(x) = y0 + x*slope
            # guards above guarantee that no slope is None
            x = (ray1._yof0 - ray2._yof0) / (ray2._slope - ray1._slope)  # type: ignore
            y = ray1.yof(x)
        return Vec2((x, y))