Beispiel #1
0
def ldelete(document: Document, layers, prob: Optional[float]) -> Document:
    """Delete one or more layers.

    LAYERS can be a single layer ID, the string 'all' (to delete all layers), or a
    coma-separated, whitespace-free list of layer IDs.

    The `--prob` option controls the probability with which each path is deleted. With a value
    lower than 1.0, some paths will not be deleted.
    """

    lids = set(multiple_to_layer_ids(layers, document))

    for lid in lids:
        if prob is not None:
            lc = LineCollection()
            for line in document[lid]:
                if not random.random() < prob:
                    lc.append(line)

            if len(lc) == 0:
                document.pop(lid)
            else:
                document[lid] = lc
        else:
            document.pop(lid)

    return document
Beispiel #2
0
def linesort(lines: LineCollection, no_flip: bool = True):
    """
    Sort lines to minimize the pen-up travel distance.

    Note: this process can be lengthy depending on the total number of line. Consider using
    `linemerge` before `linesort` to reduce the total number of line and thus significantly
    optimizing the overall plotting time.
    """
    if len(lines) < 2:
        return lines

    index = LineIndex(lines[1:], reverse=not no_flip)
    new_lines = LineCollection([lines[0]])

    while len(index) > 0:
        idx, reverse = index.find_nearest(new_lines[-1][-1])
        line = index.pop(idx)
        if reverse:
            line = np.flip(line)
        new_lines.append(line)

    logging.info(
        f"optimize: reduced pen-up (distance, mean, median) from {lines.pen_up_length()} to "
        f"{new_lines.pen_up_length()}")

    return new_lines
Beispiel #3
0
def _layout_line_collections(lc_map: Dict[Any, LineCollection], col_count: int,
                             offset: Tuple[float, float]) -> LineCollection:
    lc = LineCollection()
    for i, (key, line) in enumerate(lc_map.items()):
        lc.append(line + (i % col_count) * offset[0] +
                  math.floor(i / col_count) * offset[1] * 1j)

    return lc
Beispiel #4
0
def test_document_lid_iteration():
    lc = LineCollection([(0, 1 + 1j)])
    doc = Document()
    doc.add(lc, 1)

    for lc in doc.layers_from_ids([1, 2, 3, 4]):
        lc.append([3, 3 + 3j])

    assert doc.count() == 1
    assert len(doc.layers[1]) == 2
Beispiel #5
0
def test_vector_data_lid_iteration():
    lc = LineCollection([(0, 1 + 1j)])
    vd = VectorData()
    vd.add(lc, 1)

    for lc in vd.layers_from_ids([1, 2, 3, 4]):
        lc.append([3, 3 + 3j])

    assert vd.count() == 1
    assert len(vd.layers[1]) == 2
Beispiel #6
0
def lmove(document, sources, dest, prob: Optional[float]):
    """Move the content of one or more layer(s) to another layer.

    SOURCES can be a single layer ID, the string 'all' (to copy all non-empty layers,
    or a coma-separated, whitespace-free list of layer IDs.

    DEST can be a layer ID or the string 'new', in which case a new layer with the
    lowest available ID will be created.

    Layer(s) left empty after the move are then discarded and may thus be reused by subsequent
    commands using 'new' as destination layer.

    The `--prob` option controls the probability with which each path is moved. With a value
    lower than 1.0, some paths will not be moved to DEST, which may be used to achieve random
    coloring effects.

    If a layer is both in the source and destination, its content is not duplicated.

    Examples:

        Merge layer 1 and 2 to layer 1 (the content of layer 1 is not duplicated):

            vpype [...] lmove 1,2 1 [...]  # merge layer 1 and 2 to layer 1
    """

    src_lids = multiple_to_layer_ids(sources, document)
    dest_lid = single_to_layer_id(dest, document)

    if dest_lid in src_lids:
        src_lids.remove(dest_lid)

    for lid in src_lids:
        if prob is not None:
            # split lines with provided probability
            remaining_lines = LineCollection()
            moving_lines = LineCollection()
            for line in document.layers[lid]:
                if random.random() < prob:
                    moving_lines.append(line)
                else:
                    remaining_lines.append(line)

            if len(remaining_lines) > 0:
                document.layers[lid] = remaining_lines
            else:
                document.pop(lid)

            if len(moving_lines) > 0:
                document.add(moving_lines, dest_lid)
        else:
            document.add(document.pop(lid), dest_lid)

    return document
Beispiel #7
0
def lcopy(document, sources, dest, prob: Optional[float]):
    """Copy the content of one or more layer(s) to another layer.

    SOURCES can be a single layer ID, the string 'all' (to copy all non-empty layers,
    or a coma-separated, whitespace-free list of layer IDs.

    DEST can be a layer ID or the string 'new', in which case a new layer with the
    lowest available ID will be created.

    If a layer is both in the source and destination, its content is not duplicated.

    The `--prob` option controls the probability with which each path is copied. With a value
    lower than 1.0, some paths will not be copied to DEST, which may be used to achieve random
    coloring effects.

    Examples:

        Copy layer 1 to a new layer:

            vpype [...] lcopy 1 new [...]  # duplicate layer 1

        Make a new layer with a merged copy of layer 1 and 2:

            vpype [...] lcopy 1,2 new [...]  # make new layer with merged copy of layer 1 and 2

        Add a merged copy of all layers to layer 1. If layer 1 previously had content, this \
content is not duplicated:

            vpype [...] lcopy all 1 [...]
    """

    src_lids = multiple_to_layer_ids(sources, document)
    dest_lid = single_to_layer_id(dest, document)

    if dest_lid in src_lids:
        src_lids.remove(dest_lid)

    lc = LineCollection()
    for lid in src_lids:
        if prob is not None:
            for line in document[lid]:
                if random.random() < prob:
                    lc.append(line)
        else:
            lc.extend(document[lid])

    if len(lc) > 0:
        document.add(lc, dest_lid)

    return document
Beispiel #8
0
def holes(lines: LineCollection, hole_size):

    new_lines = LineCollection()
    for line in lines:
        ll = line_length(line)

        hole_arr = np.random.rand(math.floor(ll / 2 / hole_size)) < 0.2
        (hole_idx, ) = np.where(hole_arr)
        locations = hole_idx * 2 * hole_size

        remain = line
        for loc in np.flip(locations):
            remain, to_add = punch_hole(remain, loc, hole_size)
            if to_add is not None:
                new_lines.append(to_add)
        if remain is not None:
            new_lines.append(remain)

    return new_lines
Beispiel #9
0
def multipass(lines: LineCollection, count: int):
    """
    Add multiple passes to each line

    Each line is extended with a mirrored copy of itself, optionally multiple times. This is
    useful for pens that need several passes to ensure a good quality.
    """
    if count < 2:
        return lines

    new_lines = LineCollection()
    for line in lines:
        new_lines.append(
            np.hstack([line] + [
                line[-2::-1] if i % 2 == 0 else line[1:]
                for i in range(count - 1)
            ]))

    return new_lines
Beispiel #10
0
def occult(lines: LineCollection, tolerance: float) -> LineCollection:
    """
    Remove occulted lines.

    The order of the geometries in 'lines' matters, see example below.

    'tolerance' controls the distance tolerance between the first and last points
    of a geometry to consider it closed.

    Examples:
        $ vpype line 0 0 5 5 rect 2 2 1 1 occult show  # line is occulted by rect

        $ vpype rect 2 2 1 1 line 0 0 5 5 occult show  # line is NOT occulted by rect,
        as the line is drawn after the rectangle.
    """

    line_arr = np.array(
        [pygeos.linestrings(list(zip(line.real, line.imag))) for line in lines]
    )

    for i, line in enumerate(line_arr):
        coords = pygeos.get_coordinates(line)

        if math.hypot(coords[-1, 0] - coords[0, 0], coords[-1, 1] - coords[0, 1]) < tolerance:
            tree = pygeos.STRtree(line_arr[:i])
            p = pygeos.polygons(coords)
            geom_idx = tree.query(p, predicate="intersects")
            line_arr[geom_idx] = pygeos.set_operations.difference(line_arr[geom_idx], p)

    new_lines = LineCollection()
    for geom in line_arr:
        for i in range(pygeos.get_num_geometries(geom)):
            coords = pygeos.get_coordinates(pygeos.get_geometry(geom, i))
            new_lines.append(coords[:, 0] + coords[:, 1] * 1j)

    return new_lines
Beispiel #11
0
def test_line_collection_append(line):
    lc = LineCollection()
    lc.append(line)
    assert len(lc) == 1
    assert np.all(lc[0] == np.array([4 + 3j, 5, 10 + 10j, 5j]))
Beispiel #12
0
def _add_to_line_collection(geom: Any, lc: vp.LineCollection) -> None:
    if hasattr(geom, "exterior"):
        lc.append(geom.exterior)
        lc.extend(geom.interiors)
    else:
        lc.append(geom)