def test_complex_ellipse_with_spline_intersection(self):
     ellipse = ConstructionEllipse(center=(0, 0), major_axis=(3, 0), ratio=0.5)
     bspline = BSpline([(-4, -4), (-2, -1), (2, 1), (4, 4)])
     p1 = ellipse.flattening(distance=0.01)
     p2 = bspline.flattening(distance=0.01)
     res = intersect_polylines_2d(Vec2.list(p1), Vec2.list(p2))
     assert len(res) == 2
Esempio n. 2
0
def main():
    doc = ezdxf.new()
    doc.layers.add(ENTITIES, color=colors.YELLOW)
    doc.layers.add(INTERSECTION_POINTS, color=colors.RED)
    doc.layers.add(CURVE_APPROXIMATIONS, color=colors.CYAN)
    msp = doc.modelspace()
    ellipse = msp.add_ellipse(
        center=(0, 0),
        major_axis=(3, 0),
        ratio=0.5,
        dxfattribs=GfxAttribs(layer=ENTITIES),
    )
    fit_points = [(-4, -4), (-2, -1), (2, 1), (4, 4)]
    spline = msp.add_spline_control_frame(
        fit_points, dxfattribs=GfxAttribs(layer=ENTITIES)
    )

    p1 = Vec2.list(ellipse.flattening(distance=0.001))
    p2 = Vec2.list(spline.flattening(distance=0.001))
    msp.add_lwpolyline(p1, dxfattribs=GfxAttribs(layer=CURVE_APPROXIMATIONS))
    msp.add_lwpolyline(p2, dxfattribs=GfxAttribs(layer=CURVE_APPROXIMATIONS))
    res = intersect_polylines_2d(p1, p2)
    for point in res:
        msp.add_circle(
            center=point,
            radius=0.1,
            dxfattribs=GfxAttribs(layer=INTERSECTION_POINTS),
        )
    doc.set_modelspace_vport(height=10)
    doc.saveas(DIR / "intersect_ellipse_and_spline.dxf")
 def test_coincident_common_segment(self):
     """ The common segment does not create intersection points.
     Same as intersection of coincident lines!
     """
     pline1 = Vec2.list([(1, 1), (2, 1)])
     pline2 = Vec2.list([(1, 1), (2, 1)])
     res = intersect_polylines_2d(pline1, pline2)
     assert len(res) == 0
 def test_intersecting_squares(self):
     square1 = forms.close_polygon(forms.square(2.0))
     square2 = forms.translate(square1, (1, 1))
     res = intersect_polylines_2d(Vec2.list(square1), Vec2.list(square2))
     assert len(res) == 2
     res.sort()
     assert res[0].isclose(Vec2(1, 2))
     assert res[1].isclose(Vec2(2, 1))
 def test_zig_zag_lines_with_common_vertices(self):
     pline1 = Vec2.list([(0, 0), (2, 2), (4, 0), (6, 2), (8, 0)])
     pline2 = Vec2.list([(0, 4), (2, 2), (4, 4), (6, 2), (8, 4)])
     res = intersect_polylines_2d(pline1, pline2)
     assert len(res) == 2
     res.sort()  # do not rely on any order
     assert res[0].isclose(Vec2(2, 2))
     assert res[1].isclose(Vec2(6, 2))
Esempio n. 6
0
    def add_spline(
        self,
        fit_points: Iterable["Vertex"] = None,
        control_points: Iterable["Vertex"] = None,
        knot_values: Iterable[float] = None,
        weights: Iterable[float] = None,
        degree: int = 3,
        periodic: int = 0,
        start_tangent: "Vertex" = None,
        end_tangent: "Vertex" = None,
    ) -> "SplineEdge":
        """Add a :class:`SplineEdge`.

        Args:
            fit_points: points through which the spline must go, at least 3 fit
                points are required. list of (x, y)-tuples
            control_points: affects the shape of the spline, mandatory and
                AutoCAD crashes on invalid data. list of (x, y)-tuples
            knot_values: (knot vector) mandatory and AutoCAD crashes on invalid
                data. list of floats; `ezdxf` provides two tool functions to
                calculate valid knot values: :func:`ezdxf.math.uniform_knot_vector`,
                :func:`ezdxf.math.open_uniform_knot_vector` (default if ``None``)
            weights: weight of control point, not mandatory, list of floats.
            degree: degree of spline (int)
            periodic: 1 for periodic spline, 0 for none periodic spline
            start_tangent: start_tangent as 2d vector, optional
            end_tangent: end_tangent as 2d vector, optional

        .. warning::

            Unlike for the spline entity AutoCAD does not calculate the
            necessary `knot_values` for the spline edge itself. On the contrary,
            if the `knot_values` in the spline edge are missing or invalid
            AutoCAD **crashes**.

        """
        spline = SplineEdge()
        if fit_points is not None:
            spline.fit_points = Vec2.list(fit_points)
        if control_points is not None:
            spline.control_points = Vec2.list(control_points)
        if knot_values is not None:
            spline.knot_values = list(knot_values)
        else:
            spline.knot_values = list(
                open_uniform_knot_vector(len(spline.control_points), degree + 1)
            )
        if weights is not None:
            spline.weights = list(weights)
        spline.degree = degree
        spline.rational = int(bool(len(spline.weights)))
        spline.periodic = int(periodic)
        if start_tangent is not None:
            spline.start_tangent = Vec2(start_tangent)
        if end_tangent is not None:
            spline.end_tangent = Vec2(end_tangent)
        self.edges.append(spline)
        return spline
 def test_coincident_common_last_segment(self):
     """ The common segment does not create intersection points, but the
     preceding segment does.
     """
     pline1 = Vec2.list([(0, 0), (1, 1), (2, 1)])
     pline2 = Vec2.list([(0, 2), (1, 1), (2, 1)])
     res = intersect_polylines_2d(pline1, pline2)
     assert len(res) == 1
     assert res[0].isclose(Vec2(1, 1))
 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))
 def test_coincident_common_intermediate_segment(self):
     """ The common segment does not create intersection points, but the
     preceding and the following segment does.
     """
     pline1 = Vec2.list([(0, 0), (1, 1), (2, 1), (3, 0)])
     pline2 = Vec2.list([(0, 2), (1, 1), (2, 1), (3, 2)])
     res = intersect_polylines_2d(pline1, pline2)
     assert len(res) == 2
     res.sort()
     assert res[0].isclose(Vec2(1, 1))
     assert res[1].isclose(Vec2(2, 1))
Esempio n. 10
0
 def test_curve4_to(self):
     p = path.Path()
     p.curve4_to((4, 0, 2), (1, 1, 7), (3, 1, 5))
     mpath = path.to_matplotlib_path([p])
     assert tuple(mpath.codes) == (MC.MOVETO, MC.CURVE4, MC.CURVE4,
                                   MC.CURVE4)
     assert Vec2.list(mpath.vertices) == [(0, 0), (1, 1), (3, 1), (4, 0)]
Esempio n. 11
0
def offset_vertices_2d(vertices: Iterable['Vertex'],
                       offset: float,
                       closed: bool = False) -> Iterable['Vec2']:
    """
    Yields vertices of the offset line to the shape defined by `vertices`. The source shape consist
    of straight segments and is located in the xy-plane, the z-axis of input vertices is ignored.
    Takes closed shapes into account if argument `closed` is ``True``, which yields intersection of first and last
    offset segment as first vertex for a closed shape. For closed shapes the first and last vertex can be equal,
    else an implicit closing segment from last to first vertex is added.
    A shape  with equal first and last vertex is not handled automatically as closed shape.

    .. warning::

        Adjacent collinear segments in `opposite` directions, same as a turn by 180 degree (U-turn), leads to
        unexpected results.

    Args:
        vertices: source shape defined by vertices
        offset: line offset perpendicular to direction of shape segments defined by vertices order, offset > ``0`` is
                'left' of line segment, offset < ``0`` is 'right' of line segment
        closed: ``True`` to handle as closed shape

    """
    vertices = Vec2.list(vertices)
    if len(vertices) < 2:
        raise ValueError('2 or more vertices required.')

    if closed and not vertices[0].isclose(vertices[-1]):
        # append first vertex as last vertex to close shape
        vertices.append(vertices[0])

    # create offset segments
    offset_segments = list()
    for start, end in zip(vertices[:-1], vertices[1:]):
        offset_vec = (end - start).orthogonal().normalize(offset)
        offset_segments.append((start + offset_vec, end + offset_vec))

    if closed:  # insert last segment also as first segment
        offset_segments.insert(0, offset_segments[-1])

    # first offset vertex = start point of first segment for open shapes
    if not closed:
        yield offset_segments[0][0]

    # yield intersection points of offset_segments
    if len(offset_segments) > 1:
        for (start1, end1), (start2, end2) in zip(offset_segments[:-1],
                                                  offset_segments[1:]):
            try:  # the usual case
                yield ConstructionRay(start1, end1).intersect(
                    ConstructionRay(start2, end2))
            except ParallelRaysError:  # collinear segments
                yield end1
                if not end1.isclose(start2):  # it's an U-turn (180 deg)
                    # creates an additional vertex!
                    yield start2

    # last offset vertex = end point of last segment for open shapes
    if not closed:
        yield offset_segments[-1][1]
Esempio n. 12
0
 def test_line_to(self):
     p = path.Path()
     p.line_to((4, 5, 6))
     p.line_to((7, 8, 6))
     mpath = path.to_matplotlib_path([p])
     assert tuple(mpath.codes) == (MC.MOVETO, MC.LINETO, MC.LINETO)
     assert Vec2.list(mpath.vertices) == [(0, 0), (4, 5), (7, 8)]
Esempio n. 13
0
def convex_hull_2d(points: Iterable["Vertex"]) -> List[Vec2]:
    """Returns 2D convex hull for `points` as list of :class:`Vec2`.
    Returns a closed polyline, first vertex == last vertex.

    Args:
        points: iterable of points, z-axis is ignored

    """

    # Source: https://massivealgorithms.blogspot.com/2019/01/convex-hull-sweep-line.html?m=1
    def cross(o: Vec2, a: Vec2, b: Vec2) -> float:
        return (a - o).det(b - o)

    vertices = Vec2.list(set(points))
    vertices.sort()
    if len(vertices) < 3:
        raise ValueError(
            "Convex hull calculation requires 3 or more unique points.")

    n: int = len(vertices)
    hull: List[Vec2] = [Vec2()] * (2 * n)
    k: int = 0
    i: int
    for i in range(n):
        while k >= 2 and cross(hull[k - 2], hull[k - 1], vertices[i]) <= 0.0:
            k -= 1
        hull[k] = vertices[i]
        k += 1
    t: int = k + 1
    for i in range(n - 2, -1, -1):
        while k >= t and cross(hull[k - 2], hull[k - 1], vertices[i]) <= 0.0:
            k -= 1
        hull[k] = vertices[i]
        k += 1
    return hull[:k]
Esempio n. 14
0
 def test_two_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),
     ]
Esempio n. 15
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()
Esempio n. 16
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())
Esempio n. 17
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
Esempio n. 18
0
 def __init__(self, vertices: Iterable['Vertex'] = None):
     self.vertices: List[Vec2] = [] if vertices is None else Vec2.list(
         vertices)
Esempio n. 19
0
def test_subdivide_vec2_square_in_quads():
    b = Vec2.list(square(2))
    result = list(subdivide_face(b, quads=True))
    assert len(result) == 4
    assert result[0] == ((0, 0), (1, 0), (1, 1), (0, 1))
Esempio n. 20
0
 def polygon(vertices: Iterable["Vertex"]) -> List[Vec2]:
     _vertices = Vec2.list(vertices)
     if len(_vertices) > 1:
         if _vertices[0].isclose(_vertices[-1]):
             _vertices.pop()
     return _vertices
 def points3(self):
     return Vec2.list([(0, 0), (0, 1), (1.5, 0.75), (2, 2)])
Esempio n. 22
0
def test_inside_horiz_box():
    square = Vec2.list([(0, 0), (1, 0), (1, 1), (0, 1)])
    assert is_point_in_polygon_2d(Vec2(.5, .5), square) == 1
Esempio n. 23
0
def test_circle_inside_rect(rect):
    c = Vec2.list(circle(16, 0.7))
    result = clip_polygon_2d(rect, c, ccw_check=False)
    assert len(result) == 16
    for v in c:
        assert v in result
Esempio n. 24
0
def test_colinear_outside_horiz_box():
    square = Vec2.list([(0, 0), (1, 0), (1, 1), (0, 1)])
    assert is_point_in_polygon_2d(Vec2(1.5, 0), square) == -1
    assert is_point_in_polygon_2d(Vec2(-.5, 0), square) == -1
    assert is_point_in_polygon_2d(Vec2(0, 1.5), square) == -1
    assert is_point_in_polygon_2d(Vec2(0, -.5), square) == -1
Esempio n. 25
0
def test_borders_slanted_box_stable():
    square = Vec2.list([(0, 0), (1, 1), (0, 2), (-1, 1)])
    assert is_point_in_polygon_2d(Vec2(0.5, 0.5), square) == 0
    assert is_point_in_polygon_2d(Vec2(0.5, 1.5), square) == 0
    assert is_point_in_polygon_2d(Vec2(-.5, 1.5), square) == 0
    assert is_point_in_polygon_2d(Vec2(-.5, 0.5), square) == 0
Esempio n. 26
0
 def __init__(self, data):
     self.cities = Vec2.list(data)
Esempio n. 27
0
def test_corners_horiz_box():
    square = Vec2.list([(0, 0), (1, 0), (1, 1), (0, 1)])
    assert is_point_in_polygon_2d(Vec2(0, 0), square) == 0
    assert is_point_in_polygon_2d(Vec2(0, 1), square) == 0
    assert is_point_in_polygon_2d(Vec2(1, 1), square) == 0
    assert is_point_in_polygon_2d(Vec2(0, 1), square) == 0
Esempio n. 28
0
def test_outside_slanted_box():
    square = Vec2.list([(0, 0), (1, 1), (0, 2), (-1, 1)])
    assert is_point_in_polygon_2d(Vec2(-1, 0), square) == -1
    assert is_point_in_polygon_2d(Vec2(1, 0), square) == -1
    assert is_point_in_polygon_2d(Vec2(1, 2), square) == -1
    assert is_point_in_polygon_2d(Vec2(-1, 2), square) == -1
Esempio n. 29
0
    def export_dxf(self, tagwriter: "TagWriter") -> None:
        def set_required_tangents(points: List[Vec2]):
            if len(points) > 1:
                if self.start_tangent is None:
                    self.start_tangent = points[1] - points[0]
                if self.end_tangent is None:
                    self.end_tangent = points[-1] - points[-2]

        if len(self.weights):
            if len(self.weights) == len(self.control_points):
                self.rational = 1
            else:
                raise const.DXFValueError(
                    "SplineEdge: count of control points and count of weights "
                    "mismatch"
                )
        else:
            self.rational = 0

        write_tag = tagwriter.write_tag2
        write_tag(72, 4)  # edge type
        write_tag(94, int(self.degree))
        write_tag(73, int(self.rational))
        write_tag(74, int(self.periodic))
        write_tag(95, len(self.knot_values))  # number of knots
        write_tag(96, len(self.control_points))  # number of control points
        # build knot values list
        # knot values have to be present and valid, otherwise AutoCAD crashes
        if len(self.knot_values):
            for value in self.knot_values:
                write_tag(40, float(value))
        else:
            raise const.DXFValueError(
                "SplineEdge: missing required knot values"
            )

        # build control points
        # control points have to be present and valid, otherwise AutoCAD crashes
        cp = Vec2.list(self.control_points)
        if self.rational:
            for point, weight in zip(cp, self.weights):
                write_tag(10, float(point.x))
                write_tag(20, float(point.y))
                write_tag(42, float(weight))
        else:
            for x, y in cp:
                write_tag(10, float(x))
                write_tag(20, float(y))

        # build optional fit points
        if len(self.fit_points) > 0:
            set_required_tangents(cp)
            write_tag(97, len(self.fit_points))
            for x, y, *_ in self.fit_points:
                write_tag(11, float(x))
                write_tag(21, float(y))
        elif tagwriter.dxfversion >= const.DXF2010:
            # (97, 0) len tag required by AutoCAD 2010+
            write_tag(97, 0)

        if self.start_tangent is not None:
            x, y, *_ = self.start_tangent
            write_tag(12, float(x))
            write_tag(22, float(y))

        if self.end_tangent is not None:
            x, y, *_ = self.end_tangent
            write_tag(13, float(x))
            write_tag(23, float(y))
Esempio n. 30
0
def test_corners_slanted_box():
    square = Vec2.list([(0, 0), (1, 1), (0, 2), (-1, 1)])
    assert is_point_in_polygon_2d(Vec2(0, 0), square) == 0
    assert is_point_in_polygon_2d(Vec2(1, 1), square) == 0
    assert is_point_in_polygon_2d(Vec2(0, 2), square) == 0
    assert is_point_in_polygon_2d(Vec2(-1, 1), square) == 0