示例#1
0
def parse(geo_mapping: Dict) -> Dict:
    """Parse ``__geo_interface__`` convert all coordinates into
    :class:`Vec3` objects, Polygon['coordinates'] is always a
    tuple (exterior, holes), holes maybe an empty list.

    """
    geo_mapping = copy.deepcopy(geo_mapping)
    type_ = geo_mapping.get(TYPE)
    if type_ is None:
        raise ValueError(f'Required key "{TYPE}" not found.')

    if type_ == FEATURE_COLLECTION:
        # It is possible for this array to be empty.
        features = geo_mapping.get(FEATURES)
        if features:
            geo_mapping[FEATURES] = [parse(f) for f in features]
        else:
            raise ValueError(f'Missing key "{FEATURES}" in FeatureCollection.')
    elif type_ == GEOMETRY_COLLECTION:
        # It is possible for this array to be empty.
        geometries = geo_mapping.get(GEOMETRIES)
        if geometries:
            geo_mapping[GEOMETRIES] = [parse(g) for g in geometries]
        else:
            raise ValueError(
                f'Missing key "{GEOMETRIES}" in GeometryCollection.')
    elif type_ == FEATURE:
        # The value of the geometry member SHALL be either a Geometry object
        # or, in the case that the Feature is unlocated, a JSON null value.
        if GEOMETRY in geo_mapping:
            geometry = geo_mapping.get(GEOMETRY)
            geo_mapping[GEOMETRY] = parse(geometry) if geometry else None
        else:
            raise ValueError(f'Missing key "{GEOMETRY}" in Feature.')
    elif type_ in {
            POINT,
            LINE_STRING,
            POLYGON,
            MULTI_POINT,
            MULTI_LINE_STRING,
            MULTI_POLYGON,
    }:
        coordinates = geo_mapping.get(COORDINATES)
        if coordinates is None:
            raise ValueError(f'Missing key "{COORDINATES}" in {type_}.')
        if type_ == POINT:
            coordinates = Vec3(coordinates)
        elif type_ in (LINE_STRING, MULTI_POINT):
            coordinates = Vec3.list(coordinates)
        elif type_ == POLYGON:
            coordinates = _parse_polygon(coordinates)
        elif type_ == MULTI_LINE_STRING:
            coordinates = [Vec3.list(v) for v in coordinates]
        elif type_ == MULTI_POLYGON:
            coordinates = [_parse_polygon(v) for v in coordinates]
        geo_mapping[COORDINATES] = coordinates
    else:
        raise TypeError(f'Invalid type "{type_}".')
    return geo_mapping
示例#2
0
文件: geo.py 项目: mhenr18/ezdxf
def _parse_polygon(coordinates: Sequence) -> Sequence:
    """ Returns polygon definition as tuple (exterior, [holes]). """
    if _is_coordinate_sequence(coordinates):
        exterior = coordinates
        holes = []
    else:
        exterior = coordinates[0]
        holes = coordinates[1:]
    return Vec3.list(exterior), [Vec3.list(h) for h in holes]
示例#3
0
def test_polygon_with_holes_to_dxf_entity():
    res = cast(Hatch, list(geo.dxf_entities(POLYGON_2))[0])
    assert len(res.paths) == 3
    p = res.paths[1]
    assert p.PATH_TYPE == 'PolylinePath'
    assert p.vertices == Vec3.list(HOLE1)
    p = res.paths[2]
    assert p.PATH_TYPE == 'PolylinePath'
    assert p.vertices == Vec3.list(HOLE2)
示例#4
0
def test_polygon_with_holes_to_dxf_polygon(dxftype, polygon):
    entity = cast(DXFPolygon,
                  list(geo.dxf_entities(POLYGON_2, polygon=polygon))[0])
    assert entity.dxftype() == dxftype
    assert len(entity.paths) == 3
    p = entity.paths[1]
    assert p.type == BoundaryPathType.POLYLINE
    assert p.vertices == Vec3.list(HOLE1)
    p = entity.paths[2]
    assert p.type == BoundaryPathType.POLYLINE
    assert p.vertices == Vec3.list(HOLE2)
示例#5
0
def spline_insert_knot():
    doc = ezdxf.new("R2000", setup=True)
    msp = doc.modelspace()

    def add_spline(control_points, color=3, knots=None):
        msp.add_polyline2d(control_points,
                           dxfattribs={
                               "color": color,
                               "linetype": "DASHED"
                           })
        msp.add_open_spline(control_points,
                            degree=3,
                            knots=knots,
                            dxfattribs={"color": color})

    control_points = Vec3.list([
        (0, 0),
        (10, 20),
        (30, 10),
        (40, 10),
        (50, 0),
        (60, 20),
        (70, 50),
        (80, 70),
    ])
    add_spline(control_points, color=3, knots=None)

    bspline = BSpline(control_points, order=4)
    bspline.insert_knot(bspline.max_t / 2)
    add_spline(bspline.control_points, color=4, knots=bspline.knots())

    doc.saveas("Spline_R2000_spline_insert_knot.dxf")
示例#6
0
def test_control_vertices(p1):
    vertices = list(p1.control_vertices())
    assert vertices == Vec3.list([(0, 0), (2, 0), (2, 1), (4, 1), (4, 0)])
    path = Path()
    assert len(list(path.control_vertices())) == 0
    path = Path.from_vertices([(0, 0), (1, 0)])
    assert len(list(path.control_vertices())) == 2
示例#7
0
def best_fit_normal(vertices: Iterable['Vertex']) -> Vec3:
    """ Returns the "best fit" normal for a plane defined by three or more
    vertices. This function tolerates imperfect plane vertices. Safe function
    to detect the extrusion vector of flat arbitrary polygons.

    """
    # Source: https://gamemath.com/book/geomprims.html#plane_best_fit (9.5.3)
    vertices = Vec3.list(vertices)
    if len(vertices) < 3:
        raise ValueError("3 or more vertices required")
    first = vertices[0]
    if not first.isclose(vertices[-1]):
        vertices.append(first)  # close polygon
    prev_x, prev_y, prev_z = first.xyz
    nx = 0.0
    ny = 0.0
    nz = 0.0
    for v in vertices[1:]:
        x, y, z = v.xyz
        nx += (prev_z + z) * (prev_y - y)
        ny += (prev_x + x) * (prev_z - z)
        nz += (prev_y + y) * (prev_x - x)
        prev_x = x
        prev_y = y
        prev_z = z
    return Vec3(nx, ny, nz).normalize()
示例#8
0
    def add_mesh(self,
                 vertices: List[Vec3] = None,
                 faces: List[Sequence[int]] = None,
                 edges: List[Tuple[int, int]] = None,
                 mesh=None) -> None:
        """
        Add another mesh to this mesh.

        A `mesh` can be a :class:`MeshBuilder`, :class:`MeshVertexMerger` or :class:`~ezdxf.entities.Mesh` object
        or requires the attributes :attr:`vertices`, :attr:`edges` and :attr:`faces`.

        Args:
            vertices: list of vertices, a vertex is a ``(x, y, z)`` tuple or :class:`~ezdxf.math.Vec3` object
            faces: list of faces, a face is a list of vertex indices
            edges: list of edges, an edge is a list of vertex indices
            mesh: another mesh entity

        """
        if mesh is not None:
            vertices = Vec3.list(mesh.vertices)
            faces = mesh.faces
            edges = mesh.edges

        if vertices is None:
            raise ValueError("Requires vertices or another mesh.")
        faces = faces or []
        edges = edges or []
        indices = self.add_vertices(vertices)

        for v1, v2 in edges:
            self.edges.append((indices[v1], indices[v2]))

        for face_vertices in faces:
            self.faces.append(tuple(indices[vi] for vi in face_vertices))
示例#9
0
def test_points_from_wcs():
    points = Vec3.list([(1, 2, 3), (3, 4, 5)])
    ucs = PassTroughUCS()
    assert list(ucs.points_from_wcs(points)) == points

    ucs2 = UCS()
    assert list(ucs.points_from_wcs(points)) == list(ucs2.points_from_wcs(points))
def test_local_cubic_bspline_interpolation_from_tangents():
    points = Vec3.list(POINTS1)
    tangents = estimate_tangents(points)
    control_points, knots = local_cubic_bspline_interpolation_from_tangents(
        points, tangents)
    assert len(control_points) == 8
    assert len(knots) == 8 + 4  # count + order
示例#11
0
def test_polygon_without_holes_to_dxf_entity():
    res = cast(Hatch, list(geo.dxf_entities(POLYGON_0))[0])
    assert res.dxftype() == 'HATCH'
    assert len(res.paths) == 1
    p = res.paths[0]
    assert p.PATH_TYPE == 'PolylinePath'
    assert p.vertices == Vec3.list(EXTERIOR)
示例#12
0
def test_polygon_without_holes_to_dxf_polygon(dxftype, polygon):
    entity = cast(DXFPolygon,
                  list(geo.dxf_entities(POLYGON_0, polygon=polygon))[0])
    assert entity.dxftype() == dxftype
    assert len(entity.paths) == 1
    p = entity.paths[0]
    assert p.type == BoundaryPathType.POLYLINE
    assert p.vertices == Vec3.list(EXTERIOR)
示例#13
0
def extrude(
    profile: Iterable["Vertex"], path: Iterable["Vertex"], close=True
) -> MeshTransformer:
    """Extrude a `profile` polygon along a `path` polyline, vertices of profile
    should be in counter clockwise order.

    Args:
        profile: sweeping profile as list of (x, y, z) tuples in counter
            clockwise order
        path:  extrusion path as list of (x, y, z) tuples
        close: close profile polygon if ``True``

    Returns: :class:`~ezdxf.render.MeshTransformer`

    """

    def add_hull(bottom_profile, top_profile):
        prev_bottom = bottom_profile[0]
        prev_top = top_profile[0]
        for bottom, top in zip(bottom_profile[1:], top_profile[1:]):
            face = (
                prev_bottom,
                bottom,
                top,
                prev_top,
            )  # counter clock wise: normals outwards
            mesh.faces.append(face)
            prev_bottom = bottom
            prev_top = top

    mesh = MeshVertexMerger()
    profile = Vec3.list(profile)
    if close:
        profile = close_polygon(profile)
    path = Vec3.list(path)
    start_point = path[0]  # type: ignore
    bottom_indices = mesh.add_vertices(profile)  # base profile
    for target_point in path[1:]:  # type: ignore
        translation_vector = target_point - start_point
        # profile will just be translated
        profile = [vec + translation_vector for vec in profile]
        top_indices = mesh.add_vertices(profile)
        add_hull(bottom_indices, top_indices)
        bottom_indices = top_indices
        start_point = target_point
    return MeshTransformer.from_builder(mesh)
示例#14
0
def test_control_vertices(p1):
    vertices = list(p1.control_vertices())
    assert vertices == Vec3.list([(0, 0), (2, 0), (2, 1), (4, 1), (4, 0),
                                  (5, -1), (6, 0)])
    path = Path()
    assert len(list(path.control_vertices())) == 0
    assert list(path.control_vertices()) == list(path.approximate(2))
    path = converter.from_vertices([(0, 0), (1, 0)])
    assert len(list(path.control_vertices())) == 2
def test_knot_generation(p, method):
    fit_points = Vec3.list([(0, 0), (0, 10), (10, 10), (20, 10), (20, 0),
                            (30, 0), (30, 10), (40, 10), (40, 0)])
    count = len(fit_points)
    n = count - 1
    order = p + 1
    t_vector = distance_t_vector(fit_points)
    knots = list(knots_from_parametrization(n, p, t_vector, method))
    check_knots(n + 1, p + 1, knots)
示例#16
0
def cubic_bezier_interpolation(
        points: Iterable['Vertex']) -> Iterable[Bezier4P]:
    """ Returns an interpolation curve for given data `points` as multiple cubic
    Bézier-curves. Returns n-1 cubic Bézier-curves for n given data points,
    curve i goes from point[i] to point[i+1].

    Args:
        points: data points

    .. versionadded:: 0.13

    """
    # Source: https://towardsdatascience.com/b%C3%A9zier-interpolation-8033e9a262c2
    points = Vec3.list(points)
    if len(points) < 3:
        raise ValueError('At least 3 points required.')

    num = len(points) - 1

    # setup tri-diagonal matrix (a, b, c)
    b = [4.0] * num
    a = [1.0] * num
    c = [1.0] * num
    b[0] = 2.0
    b[num - 1] = 7.0
    a[num - 1] = 2.0

    # setup right-hand side quantities
    points_vector = [points[0] + 2.0 * points[1]]
    points_vector.extend(2.0 * (2.0 * points[i] + points[i + 1])
                         for i in range(1, num - 1))
    points_vector.append(8.0 * points[num - 1] + points[num])

    # solve tri-diagonal linear equation system
    solution = tridiagonal_matrix_solver((a, b, c), points_vector)
    control_points_1 = Vec3.list(solution.rows())
    control_points_2 = [
        p * 2.0 - cp for p, cp in zip(points[1:], control_points_1[1:])
    ]
    control_points_2.append((control_points_1[num - 1] + points[num]) / 2.0)

    for defpoints in zip(points, control_points_1, control_points_2,
                         points[1:]):
        yield Bezier4P(defpoints)
示例#17
0
def ngon_to_triangles(face: Iterable["Vertex"]) -> Iterable[Sequence[Vec3]]:
    _face = Vec3.list(face)
    if _face[0].isclose(_face[-1]):  # closed shape
        center = Vec3.sum(_face[:-1]) / (len(_face) - 1)
    else:
        center = Vec3.sum(_face) / len(_face)
        _face.append(_face[0])

    for v1, v2 in zip(_face[:-1], _face[1:]):
        yield v1, v2, center
示例#18
0
 def from_vertices(cls, vertices: Iterable['Vertex'], close=False) -> 'Path':
     """ Returns a :class:`Path` from vertices.  """
     vertices = Vec3.list(vertices)
     if len(vertices) < 2:
         return cls()
     path = cls(start=vertices[0])
     for vertex in vertices[1:]:
         path.line_to(vertex)
     if close:
         path.close()
     return path
示例#19
0
def from_vertices(vertices: Iterable['Vertex'], close=False) -> Path:
    """ Returns a :class:`Path` object from the given `vertices`.  """
    vertices = Vec3.list(vertices)
    if len(vertices) < 2:
        return Path()
    path = Path(start=vertices[0])
    for vertex in vertices[1:]:
        path.line_to(vertex)
    if close:
        path.close()
    return path
示例#20
0
def from_vertices(vertices: Iterable["Vertex"], close=False) -> Path:
    """Returns a :class:`Path` object from the given `vertices`."""
    _vertices = Vec3.list(vertices)
    if len(_vertices) < 2:
        return Path()
    path = Path(start=_vertices[0])
    for vertex in _vertices[1:]:
        if not path.end.isclose(vertex):
            path.line_to(vertex)
    if close:
        path.close()
    return path
示例#21
0
def fit_points_2():
    return Vec3.list([
        (0, 0),
        (0, 10),
        (10, 10),
        (20, 10),
        (20, 0),
        (30, 0),
        (30, 10),
        (40, 10),
        (40, 0),
    ])
示例#22
0
    def __init__(self, points: Iterable["Vertex"] = None, segments: int = 100):
        """
        Args:
            points: spline definition points
            segments: count of line segments for approximation, vertex count is
                `segments` + 1

        """
        if points is None:
            points = []
        self.points: List[Vec3] = Vec3.list(points)
        self.segments = int(segments)
示例#23
0
def test_arc_distances():
    p = Vec3.list([(0, 0), (2, 2), (4, 0), (6, -2), (8, 0)])
    # p[1]..p[3] are a straight line, radius calculation fails and
    # a straight line from p[1] to p[2] is used as replacement
    # for the second arc
    radius = 2.0
    arc_length = math.pi * 0.5 * radius
    diagonal = math.sqrt(2.0) * radius
    distances = list(arc_distances(p))
    assert len(distances) == 4
    assert isclose(distances[0], arc_length)
    assert isclose(distances[1], diagonal)  # replacement for arc
    assert isclose(distances[2], arc_length)
    assert isclose(distances[3], arc_length)
示例#24
0
    def __init__(
        self,
        vertices: Iterable[Vertex],
        close: bool = False,
        rel_tol: float = REL_TOL,
    ):
        self._rel_tol = float(rel_tol)
        v3list: List[Vec3] = Vec3.list(vertices)
        self._vertices: List[Vec3] = v3list
        if close and len(v3list) > 2:
            if not v3list[0].isclose(v3list[-1], rel_tol=self._rel_tol):
                v3list.append(v3list[0])

        self._distances: List[float] = _distances(v3list)
示例#25
0
def area(vertices: Iterable['Vertex']) -> float:
    """ Returns the area of a polygon, returns the projected area in the
    xy-plane for 3D vertices.
    """
    vertices = Vec3.list(vertices)
    if len(vertices) < 3:
        raise ValueError('At least 3 vertices required.')

    # Close polygon:
    if not vertices[0].isclose(vertices[-1]):
        vertices.append(vertices[0])

    return abs(sum(
        (p1.x * p2.y - p1.y * p2.x) for p1, p2 in zip(vertices, vertices[1:])
    ) / 2)
示例#26
0
    def __init__(
        self,
        control_points: Iterable["Vertex"],
        degree: int = 2,
        closed: bool = True,
    ):
        """
        Args:
            control_points: B-spline control frame vertices
            degree: degree of B-spline, only 2 and 3 is supported
            closed: ``True`` for closed curve

        """
        self.control_points = Vec3.list(control_points)
        self.degree = degree
        self.closed = closed
示例#27
0
    def extend(self, vertices: Iterable["Vertex"]) -> None:
        """Append multiple vertices to the reference line.

        It is possible to work with 3D vertices, but all vertices have to be in
        the same plane and the normal vector of this plan is stored as
        extrusion vector in the MLINE entity.

        """
        vertices = Vec3.list(vertices)
        if not vertices:
            return
        all_vertices = []
        if len(self):
            all_vertices.extend(self.get_locations())
        all_vertices.extend(vertices)
        self.generate_geometry(all_vertices)
示例#28
0
def has_clockwise_orientation(vertices: Iterable['Vertex']) -> bool:
    """ Returns True if 2D `vertices` have clockwise orientation. Ignores
    z-axis of all vertices.

    Args:
        vertices: iterable of :class:`Vec2` compatible objects

    Raises:
        ValueError: less than 3 vertices

    """
    vertices = Vec3.list(vertices)
    if len(vertices) < 3:
        raise ValueError('At least 3 vertices required.')

    # Close polygon:
    if not vertices[0].isclose(vertices[-1]):
        vertices.append(vertices[0])

    return sum(
        (p2.x - p1.x) * (p2.y + p1.y)
        for p1, p2 in zip(vertices, vertices[1:])
    ) > 0
示例#29
0
def test_polygon_mapping_vertex_count_error(points):
    with pytest.raises(ValueError):
        geo.polygon_mapping(Vec3.list(points), [])
示例#30
0
def test_line_string_to_dxf_entity():
    res = cast(LWPolyline, list(geo.dxf_entities(LINE_STRING))[0])
    assert res.dxftype() == 'LWPOLYLINE'
    assert list(res.vertices()) == Vec3.list(EXTERIOR)