Exemplo n.º 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
Exemplo n.º 2
0
def circles(
    vector_data: Document,
    count,
    delta,
    quantization,
    layer_count,
    random_layer,
    layer,
    offset,
):

    start_layer_id = single_to_layer_id(layer, vector_data)
    for i in range(count):
        if random_layer:
            lid = start_layer_id + random.randint(0, layer_count - 1)
        else:
            lid = start_layer_id + (i % layer_count)

        vector_data.add(
            LineCollection([
                circle(
                    (i + 1) * delta, quantization) + offset[0] + 1j * offset[1]
            ]),
            lid,
        )

    return vector_data
Exemplo n.º 3
0
def efill(document: vp.Document, tolerance: float, distance: float):
    """
    Implements the Eulerian fill algorithm which fills any closed shapes with as few paths as there are contiguous
    regions. With scanlines to fill any shapes, even those with holes, with an even-odd fill order and direct pathing.

    """
    for layer in list(document.layers.values()
                      ):  # Add all the closed paths to the efill.
        efill = EulerianFill(distance)
        for p in layer:
            if np.abs(p[0] - p[-1]) <= tolerance:
                efill += vp.as_vector(p)
        fill = efill.get_fill()  # Get the resulting fill.

        lc = vp.LineCollection()
        cur_line = []
        for pt in fill:
            if pt is None:
                if cur_line:
                    lc.append(cur_line)
                cur_line = []
            else:
                cur_line.append(complex(pt[0], pt[1]))
        if cur_line:
            lc.append(cur_line)
        document.add(lc)
    return document
Exemplo n.º 4
0
def dread(
    document: vp.Document,
    file,
    quantization: float,
    simplify: bool,
    parallel: bool,
    query: str,
    groupby: str,
) -> vp.Document:
    """
    Extract geometries from a DXF file.
    """
    dxf = ezdxf.readfile(file)
    elements = []
    unit = dxf.header.get("$INSUNITS")

    # TODO: Load this into correct units.
    if unit is not None and unit != 0:
        du = units.DrawingUnits(96.0, unit="in")
        scale = du.factor(decode(unit))
    else:
        scale = 1

    all_entities_by_attribute = dxf.query(query=query).groupby(groupby)
    for group in all_entities_by_attribute.values():
        for entity in group:
            entity_to_svg(elements, dxf, entity, scale)
        lc = i_trample_your_api(elements, quantization, simplify, parallel)
        document.add(lc)
        elements.clear()
    return document
Exemplo n.º 5
0
    def process(self, processors):
        document = Document()

        for _ in range(self.number):
            state = execute_processors(processors)
            document.extend(state.document)

        return document
Exemplo n.º 6
0
    def process(self, processors):
        document = Document()

        for i in range(self.number[0]):
            for j in range(self.number[1]):
                state = execute_processors(processors)
                state.document.translate(self.offset[0] * i, self.offset[1] * j)
                document.extend(state.document)

        return document
Exemplo n.º 7
0
def iread(document: vp.Document, input_file: str, color, distance: float):
    """
    Image Read and Vectorization.

    This is a pure python polygon producer. The goal of this project is to
    vector trace images according to some given criteria. The default mode
    does black v. white. However, multiple colors can be specified along with
    a color distance and those colors will be extracted and traced.
    """
    image = Image.open(input_file)
    width, height = image.size
    if len(color) == 0:
        if image.mode != 'L':
            image = image.convert('L')
        image = image.point(lambda e: int(e > 127) * 255)
        lc = vp.LineCollection()
        document.add(lc)
        for points in _vectrace(image.load(), width, height):
            lc.append(points)
        return document

    distance_sq = distance * distance

    def dist(c, pixel):
        r = c.red - pixel[0]
        g = c.green - pixel[1]
        b = c.blue - pixel[2]
        return r * r + g * g + b * b <= distance_sq

    if image.mode != "RGBA":
        image = image.convert("RGBA")

    for c in color:
        v = Image.new('L', image.size, 255)
        v_data = v.load()
        new_data = image.load()
        for y in range(height):
            for x in range(width):
                pixel = new_data[x, y]
                if pixel[3] == 0:
                    continue
                if dist(c, pixel):
                    new_data[x, y] = (255, 255, 255, 0)
                    v_data[x, y] = 0

        lc = vp.LineCollection()
        document.add(lc)
        for points in _vectrace(v_data, width, height):
            lc.append(points)
    return document
Exemplo n.º 8
0
def ldelete(document: Document, layers) -> 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.
    """

    lids = set(multiple_to_layer_ids(layers, document))

    new_doc = document.empty_copy()
    for lid in document.ids():
        if lid not in lids:
            new_doc[lid] = document[lid]

    return new_doc
Exemplo n.º 9
0
def pagesize(document: vp.Document, size, landscape) -> vp.Document:
    """Change the current page size.

    The page size is set (or modified) by the `read` command and used by the `write` command by
    default. This command can be used to set it to an arbitrary value. See the `write` command
    help section for more information on valid size value (`vpype write --help`).

    Note: this command only changes the current page size and has no effect on the geometries.
    Use the `translate` and `scale` commands to change the position and/or the scale of the
    geometries.

    Examples:

        Set the page size to A4:

            vpype [...] pagesize a4 [...]

        Set the page size to landscape A4:

            vpype [...] pagesize --landscape a4 [...]

        Set the page size to 11x15in:

            vpype [...] pagesize 11inx15in [...]
    """

    document.page_size = _normalize_page_size(size, landscape)
    return document
Exemplo n.º 10
0
def trim(document: vp.Document, margin_x: float, margin_y: float,
         layer: Union[int, List[int]]) -> vp.Document:
    """Trim the geometries by some margin.

    This command trims the geometries by the provided X and Y margins with respect to the
    current bounding box.

    By default, `trim` acts on all layers. If one or more layer IDs are provided with the
    `--layer` option, only these layers will be affected. In this case, the bounding box is
    that of the listed layers.
    """

    layer_ids = vp.multiple_to_layer_ids(layer, document)
    bounds = document.bounds(layer_ids)

    if not bounds:
        return document

    min_x = bounds[0] + margin_x
    max_x = bounds[2] - margin_x
    min_y = bounds[1] + margin_y
    max_y = bounds[3] - margin_y
    if min_x > max_x:
        min_x = max_x = 0.5 * (min_x + max_x)
    if min_y > max_y:
        min_y = max_y = 0.5 * (min_y + max_y)

    for vid in layer_ids:
        lc = document[vid]
        lc.crop(min_x, min_y, max_x, max_y)

    return document
Exemplo n.º 11
0
    def run_plot(
        document: vp.Document,
        rotate: bool,
        layer_visibility: Mapping[int, bool],
    ):
        """caution: axy must be pre-configured!"""
        if document is None:
            Logger.warning("self.document is None")
            return

        doc = document.empty_copy()

        for layer_id in document.layers:
            if layer_visibility.get(layer_id, True):
                Logger.info(f"adding layer {layer_id}")
                doc.layers[layer_id] = document.layers[layer_id]

        if rotate:
            Logger.info("rotating SVG")
            doc = copy.deepcopy(doc)
            doc.rotate(-math.pi / 2)
            doc.translate(0, doc.page_size[0])
            doc.page_size = tuple(reversed(doc.page_size))

        # convert to SVG
        str_io = io.StringIO()
        vp.write_svg(str_io, doc)
        svg = str_io.getvalue()

        # plot
        axy.plot_svg(svg)
Exemplo n.º 12
0
def stat(document: Document):
    """Print human-readable statistics on the current geometries."""

    print("========= Stats ========= ")
    print(f"Current page size: {document.page_size}")
    length_tot = 0.0
    pen_up_length_tot = 0.0
    for layer_id in sorted(document.layers.keys()):
        layer = document.layers[layer_id]
        length = layer.length()
        pen_up_length, pen_up_mean, pen_up_median = layer.pen_up_length()
        length_tot += length
        pen_up_length_tot += pen_up_length
        print(f"Layer {layer_id}")
        print(f"  Length: {length}")
        print(f"  Pen-up length: {pen_up_length}")
        print(f"  Total length: {length + pen_up_length}")
        print(f"  Mean pen-up length: {pen_up_mean}")
        print(f"  Median pen-up length: {pen_up_median}")
        print(f"  Path count: {len(layer)}")
        print(f"  Segment count: {layer.segment_count()}")
        print(
            f"  Mean segment length:",
            str(length /
                layer.segment_count() if layer.segment_count() else "n/a"),
        )
        print(f"  Bounds: {layer.bounds()}")
    print(f"Totals")
    print(f"  Layer count: {len(document.layers)}")
    print(f"  Length: {length_tot}")
    print(f"  Pen-up length: {pen_up_length_tot}")
    print(f"  Total length: {length_tot + pen_up_length_tot}")
    print(
        f"  Path count: {sum(len(layer) for layer in document.layers.values())}"
    )
    print(f"  Segment count: {document.segment_count()}")

    print(
        f"  Mean segment length:",
        str(length_tot /
            document.segment_count() if document.segment_count() else "n/a"),
    )
    print(f"  Bounds: {document.bounds()}")
    print("========================= ")

    return document
Exemplo n.º 13
0
def occult(
    document: vpype.Document,
    tolerance: float,
    layer: Optional[Union[int, List[int]]],
    keep_occulted: bool = False,
) -> vpype.Document:
    """
    Remove lines occulted by polygons.
    The 'keep_occulted' option (-k, --keep-occulted) saves removed geometries in a new layer.
    The order of the geometries in 'lines' matters, see basic example below.
    Occlusion is performed layer by layer. This means that if one geometry is occulting another,
    and these geometries are not in the same layer, occult won't remove occulted paths.

    Args:
        document: the vpype.Document to work on.
        tolerance: controls the distance tolerance between the first and last points
        of a geometry to consider it closed.
        layer: specify which layer(s) to work on. Default: all.
        keep_occulted: If set, this flag allows to save removed lines in a separate layer.

    Examples:

        - Basic usage:
            $ vpype line 0 0 5cm 5cm rect 2cm 2cm 1cm 1cm occult show  # line is occulted by rect
            $ vpype rect 2cm 2cm 1cm 1cm line 0 0 5cm 5cm occult show  # line is NOT occulted by rect,
            as the line is drawn after the rectangle.

        - Keep occulted lines in a separate layer:
            $ vpype line -- -3cm 0 8cm 0  circle 0 0 3cm  circle -l 2 5cm 0 3cm occult -k show
            # 'occult -k' will remove the path inside the first circle, and put it in a third layer.
            # both the first circle and the line are not affected by the second circle, as it is
            # in a different layer.
    """

    new_document = document.empty_copy()
    layer_ids = multiple_to_layer_ids(layer, document)
    removed_layer_id = document.free_id()

    for lines, l_id in zip(document.layers_from_ids(layer_ids), layer_ids):
        lines, removed_lines = _occult_layer(lines, tolerance, keep_occulted)
        new_document.add(lines, layer_id=l_id)

        if keep_occulted and not removed_lines.is_empty():
            new_document.add(removed_lines, layer_id=removed_layer_id)

    return new_document
Exemplo n.º 14
0
def test_document_bounds_empty_layer():
    doc = Document()

    doc.add(LineCollection([(0, 10 + 10j)]), 1)
    doc.add(LineCollection())

    assert doc.bounds() == (0, 0, 10, 10)
Exemplo n.º 15
0
    def __init__(self, data: Dict[str, Any]):
        self.count = data["count"]
        self.length = data.get("length", 0)
        self.pen_up_length = data.get("pen_up_length", 0)
        self.bounds = data.get("bounds", [0, 0, 0, 0])
        self.layers = data.get("layers", {})

        self.document = Document()
        for vid, lines in self.layers.items():
            self.document[int(vid)] = LineCollection(
                [np.array([x + 1j * y for x, y in line]) for line in lines])
Exemplo n.º 16
0
def dbsample(document: Document):
    """
    Show statistics on the current geometries in JSON format.
    """
    global debug_data

    data: Dict[str, Any] = {}
    if document.is_empty():
        data["count"] = 0
    else:
        data["count"] = sum(len(lc) for lc in document.layers.values())
        data["layer_count"] = len(document.layers)
        data["length"] = document.length()
        data["pen_up_length"] = document.pen_up_length()
        data["bounds"] = document.bounds()
        data["layers"] = {
            layer_id: [as_vector(line).tolist() for line in layer]
            for layer_id, layer in document.layers.items()
        }

    debug_data.append(data)
    return document
Exemplo n.º 17
0
def layout(
    document: vp.Document,
    size: Tuple[float, float],
    landscape: bool,
    margin: Optional[float],
    align: str,
    valign: str,
) -> vp.Document:
    """Layout command"""

    size = _normalize_page_size(size, landscape)

    document.page_size = size
    bounds = document.bounds()

    if bounds is None:
        # nothing to layout
        return document

    min_x, min_y, max_x, max_y = bounds
    width = max_x - min_x
    height = max_y - min_y
    if margin is not None:
        document.translate(-min_x, -min_y)
        scale = min((size[0] - 2 * margin) / width,
                    (size[1] - 2 * margin) / height)
        document.scale(scale)
        min_x = min_y = 0.0
        width *= scale
        height *= scale
    else:
        margin = 0.0

    if align == "left":
        h_offset = margin - min_x
    elif align == "right":
        h_offset = size[0] - margin - width - min_x
    else:
        h_offset = margin + (size[0] - width - 2 * margin) / 2 - min_x

    if valign == "top":
        v_offset = margin - min_y
    elif valign == "bottom":
        v_offset = size[1] - margin - height - min_y
    else:
        v_offset = margin + (size[1] - height - 2 * margin) / 2 - min_y

    document.translate(h_offset, v_offset)
    return document
Exemplo n.º 18
0
def test_document_empty_copy():
    doc = Document()
    doc.add(LineCollection([(0, 1)]), 1)
    doc.page_size = 3, 4

    new_doc = doc.empty_copy()
    assert len(new_doc.layers) == 0
    assert new_doc.page_size == (3, 4)
Exemplo n.º 19
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
Exemplo n.º 20
0
def _compute_origin(
    document: vp.Document,
    layer: Optional[Union[int, List[int]]],
    origin_coords: Optional[Union[Tuple[()], Tuple[float, float]]],
) -> Tuple[Tuple[float, float], List[int], Tuple[float, float, float, float]]:
    layer_ids = vp.multiple_to_layer_ids(layer, document)
    bounds = document.bounds(layer_ids)

    if not bounds:
        logging.warning("no geometry available, cannot compute origin")
        raise ValueError

    if origin_coords is not None and len(origin_coords) == 2:
        origin = origin_coords
    else:
        origin = (
            0.5 * (bounds[0] + bounds[2]),
            0.5 * (bounds[1] + bounds[3]),
        )

    return cast(Tuple[float, float], origin), layer_ids, bounds
Exemplo n.º 21
0
def eread(document: vp.Document, filename: str):
    # populate the vp_source[s] properties
    document.set_property(vp.METADATA_FIELD_SOURCE,
                          pathlib.Path(filename).absolute())
    document.add_to_sources(filename)

    pattern = EmbPattern(filename)
    for stitches, color in pattern.get_as_stitchblock():
        if len(stitches) == 0:
            continue
        lc = vp.LineCollection()
        lc.scale(1.0 / _EMB_SCALE_FACTOR)
        stitch_block = np.asarray(stitches, dtype="float")
        stitch_block = stitch_block[..., 0] + 1j * stitch_block[..., 1]
        lc.append(stitch_block)
        lc.set_property(vp.METADATA_FIELD_COLOR, vp.Color(color.hex_color()))
        document.add(lc, with_metadata=True)
    return document
Exemplo n.º 22
0
def layout(
    document: vp.Document,
    size: Tuple[float, float],
    landscape: bool,
    margin: Optional[float],
    align: str,
    valign: str,
) -> vp.Document:
    """Layout the geometries on the provided page size.

    By default, this command centers everything on the page. The horizontal and vertical
    alignment can be adjusted using the `--align`, resp. `--valign` options.

    Optionally, this command can scale the geometries to fit specified margins with the
    `--fit-to-margin` option.

    Examples:

        Fit the geometries to 3cm margins with top alignment (a generally pleasing arrangement
        for square designs on portrait-oriented pages):

            vpype read input.svg layout --fit-to-margin 3cm --valign top a4 write.svg
    """

    if landscape and size[0] < size[1]:
        size = size[::-1]

    document.page_size = size
    bounds = document.bounds()

    if bounds is None:
        # nothing to layout
        return document

    min_x, min_y, max_x, max_y = bounds
    width = max_x - min_x
    height = max_y - min_y
    if margin is not None:
        document.translate(-min_x, -min_y)
        scale = min((size[0] - 2 * margin) / width,
                    (size[1] - 2 * margin) / height)
        document.scale(scale)
        min_x = min_y = 0.0
        width *= scale
        height *= scale
    else:
        margin = 0.0

    if align == "left":
        h_offset = margin - min_x
    elif align == "right":
        h_offset = size[0] - margin - width - min_x
    else:
        h_offset = margin + (size[0] - width - 2 * margin) / 2 - min_x

    if valign == "top":
        v_offset = margin - min_y
    elif valign == "bottom":
        v_offset = size[1] - margin - height - min_y
    else:
        v_offset = margin + (size[1] - height - 2 * margin) / 2 - min_y

    document.translate(h_offset, v_offset)
    return document
Exemplo n.º 23
0
def test_ops_on_document_with_emtpy_layer():
    doc = Document()
    lc = LineCollection()
    doc.add(lc, 1)
    _all_document_ops(doc)
Exemplo n.º 24
0
def test_ops_on_emtpy_document():
    doc = Document()
    _all_document_ops(doc)
Exemplo n.º 25
0
def _all_document_ops(doc: Document):
    doc.bounds()
    doc.length()
    doc.segment_count()
Exemplo n.º 26
0
def mdgrid(
    document: vp.Document,
    seed: Optional[int],
    size,
    count,
    pen_width,
    fat_grid,
    global_rate,
    rate_fill,
    rate_gradient,
    rate_bigdot,
    rate_star,
    rate_hatch,
):
    """Create nice random grids with stuff in them."""

    if len(rate_fill) == 0 and global_rate is not None:
        rate_fill = [global_rate]
    rate_gradient = check_default(rate_gradient, global_rate)
    rate_bigdot = check_default(rate_bigdot, global_rate)
    rate_star = check_default(rate_star, global_rate)
    rate_hatch = check_default(rate_hatch, global_rate)

    logging.info(
        f"mdgrid: rates: fill = {rate_fill}, gradient = {rate_gradient}, "
        f"bigdot = {rate_bigdot}, star = {rate_star}, hatch = {rate_hatch}")

    # handle seed
    if seed is None:
        seed = random.randint(0, int(1e9))
        logging.info(f"mdgrid: no seed provided, generating one ({seed})")
    np.random.seed(seed)
    random.seed(seed)

    grid_lc = vp.LineCollection()

    # build the grid
    col_widths = distribute_widths(count[0], size[0])
    row_widths = distribute_widths(count[1], size[1])
    col_seps = np.hstack([0, np.cumsum(col_widths)])
    row_seps = np.hstack([0, np.cumsum(row_widths)])

    # outer boundaries must be a single loop (for fat grid to work nicely)
    grid_lc.append([
        col_seps[0] + row_seps[0] * 1j,
        col_seps[0] + row_seps[-1] * 1j,
        col_seps[-1] + row_seps[-1] * 1j,
        col_seps[-1] + row_seps[0] * 1j,
        col_seps[0] + row_seps[0] * 1j,
    ])
    grid_lc.extend([x + row_seps[0] * 1j, x + row_seps[-1] * 1j]
                   for x in col_seps)
    grid_lc.extend([y * 1j + col_seps[0], y * 1j + col_seps[-1]]
                   for y in row_seps)

    # implement fat grid
    fat_grid_lc = vp.LineCollection()
    if fat_grid:
        mls = grid_lc.as_mls()
        fat_grid_lc.extend(
            unary_union([
                mls_parallel_offset(mls, pen_width, "left"),
                mls_parallel_offset(mls, pen_width, "right"),
            ]))

    # generate content in each cell
    fill_lcs = [vp.LineCollection() for _ in range(len(rate_fill))]
    grad_lc = vp.LineCollection()
    bigdot_lc = vp.LineCollection()
    star_lc = vp.LineCollection()
    hatch_lc = vp.LineCollection()
    for (x, y) in itertools.product(range(count[0]), range(count[1])):
        rect = (
            col_seps[x],
            row_seps[y],
            col_seps[x + 1] - col_seps[x],
            row_seps[y + 1] - row_seps[y],
        )

        filled = False
        for i, r in enumerate(rate_fill):
            if random.random() < r:
                fill_lcs[i].extend(generate_fill(rect, pen_width))
                filled = True
                break
        if not filled:
            if random.random() < rate_gradient:
                grad_lc.extend(
                    generate_dot_gradient(rect, pen_width, density=0.3))
            elif random.random() < rate_bigdot:
                bigdot_lc.extend(
                    generate_big_dot_gradient(rect, pen_width, 3,
                                              density=0.01))
            elif random.random() < rate_star:
                star_lc.extend(generate_star(rect, line_count=20))
            elif random.random() < rate_hatch:
                hatch_lc.extend(generate_hatch(rect))

    # populate vector data with layer content
    document.add(grid_lc, 1)
    document.add(fat_grid_lc, 2)

    document.add(star_lc, 3)
    document.add(hatch_lc, 4)

    document.add(grad_lc, 5)

    document.add(bigdot_lc, 6)

    for i, lc in enumerate(fill_lcs):
        document.add(lc, 7 + i)

    return document
Exemplo n.º 27
0
def test_document_bounds():
    doc = Document()
    doc.add(LineCollection([(-10, 10), (0, 0)]), 1)
    doc.add(LineCollection([(0, 0), (-10j, 10j)]), 2)
    assert doc.bounds() == (-10, -10, 10, 10)
Exemplo n.º 28
0
def read(
    document: Document,
    file,
    single_layer: bool,
    layer: Optional[int],
    quantization: float,
    simplify: bool,
    parallel: bool,
    no_crop: bool,
    display_size: Tuple[float, float],
    display_landscape: bool,
) -> Document:
    """Extract geometries from a SVG file.

    By default, the `read` command attempts to preserve the layer structure of the SVG. In this
    context, top-level groups (<svg:g>) are each considered a layer. If any, all non-group,
    top-level SVG elements are imported into layer 1.

    The following logic is used to determine in which layer each SVG top-level group is
    imported:

        - If a `inkscape:label` attribute is present and contains digit characters, it is \
stripped of non-digit characters the resulting number is used as target layer. If the \
resulting number is 0, layer 1 is used instead.

        - If the previous step fails, the same logic is applied to the `id` attribute.

        - If both previous steps fail, the target layer matches the top-level group's order \
of appearance.

    Using `--single-layer`, the `read` command operates in single-layer mode. In this mode, \
all geometries are in a single layer regardless of the group structure. The current target \
layer is used default and can be specified with the `--layer` option.

    This command only extracts path elements as well as primitives (rectangles, ellipses,
    lines, polylines, polygons). Other elements such as text and bitmap images are discarded,
    and so is all formatting.

    All curved primitives (e.g. bezier path, ellipses, etc.) are linearized and approximated by
    polylines. The quantization length controls the maximum length of individual segments.

    Optionally, a line simplification with tolerance set to quantization can be applied on the
    SVG's curved element (e.g. circles, ellipses, arcs, bezier curves, etc.). This is enabled
    with the `--simplify` flag. This process reduces significantly the number of segments used
    to approximate the curve while still guaranteeing an accurate conversion, but may increase
    the execution time of this command.

    The `--parallel` option enables multiprocessing for the SVG conversion. This is recommended
    ONLY when using `--simplify` on large SVG files with many curved elements.

    By default, the geometries are cropped to the SVG boundaries defined by its width and
    length attributes. The crop operation can be disabled with the `--no-crop` option.

    In general, SVG boundaries are determined by the `width` and `height` of the top-level
    <svg> tag. However, the some SVG may have their width and/or height specified as percent
    value or even miss them altogether (in which case they are assumed to be set to 100%). In
    these cases, vpype considers by default that 100% corresponds to a A4 page in portrait
    orientation. The options `--display-size FORMAT` and `--display-landscape` can be used
    to specify a different format.

    Examples:

        Multi-layer import:

            vpype read input_file.svg [...]

        Single-layer import:

            vpype read --single-layer input_file.svg [...]

        Single-layer import with target layer:

            vpype read --single-layer --layer 3 input_file.svg [...]

        Multi-layer import with specified quantization and line simplification enabled:

            vpype read --quantization 0.01mm --simplify input_file.svg [...]

        Multi-layer import with cropping disabled:

            vpype read --no-crop input_file.svg [...]
    """

    width, height = display_size
    if display_landscape:
        width, height = height, width

    if single_layer:
        lc, width, height = read_svg(
            file,
            quantization=quantization,
            crop=not no_crop,
            simplify=simplify,
            parallel=parallel,
            default_width=width,
            default_height=height,
        )

        document.add(lc, single_to_layer_id(layer, document))
        document.extend_page_size((width, height))
    else:
        if layer is not None:
            logging.warning("read: target layer is ignored in multi-layer mode")
        document.extend(
            read_multilayer_svg(
                file,
                quantization=quantization,
                crop=not no_crop,
                simplify=simplify,
                parallel=parallel,
                default_width=width,
                default_height=height,
            )
        )

    return document
Exemplo n.º 29
0
def occult(
    document: vpype.Document,
    tolerance: float,
    layer: Optional[Union[int, List[int]]],
    keep_occulted: bool = False,
    ignore_layers: bool = False,
    reverse: bool = False,
) -> vpype.Document:
    """
    Remove lines occulted by polygons.
    The 'keep_occulted' option (-k, --keep-occulted) saves removed geometries in a new layer.
    The order of the geometries in 'lines' matters, see basic example below.
    Occlusion is performed layer by layer. This means that if one geometry is occulting another,
    and these geometries are not in the same layer, occult won't remove occulted paths.
    With the 'ignore_layers' option, occlusion is performed on all geometry regardless
    of layers, with higher-numbered layers occluding lower-numbered layers.

    Args:
        document: the vpype.Document to work on.
        tolerance: controls the distance tolerance between the first and last points
        of a geometry to consider it closed.
        layer: specify which layer(s) to work on. Default: all.
        keep_occulted: If set, this flag allows to save removed lines in a separate layer.
        ignore_layers: If set, this flag causes occult to treat all geometries as if they
        exist on the same layer. However, all geometries in the final result
        remain on their original layer.

    Examples:

        - Basic usage:
            $ vpype line 0 0 5cm 5cm rect 2cm 2cm 1cm 1cm occult show  # line is occulted by rect
            $ vpype rect 2cm 2cm 1cm 1cm line 0 0 5cm 5cm occult show  # line is NOT occulted by rect,
            as the line is drawn after the rectangle.

        - Keep occulted lines in a separate layer:
            $ vpype line -- -3cm 0 8cm 0  circle 0 0 3cm  circle -l 2 5cm 0 3cm occult -k show
            # 'occult -k' will remove the path inside the first circle, and put it in a third layer.
            # both the first circle and the line are not affected by the second circle, as it is
            # in a different layer.
    """
    new_document = document.empty_copy()
    layer_ids = multiple_to_layer_ids(layer, document)
    removed_layer_id = document.free_id()

    if ignore_layers:
        layers = [{
            l_id: list(document.layers_from_ids([l_id]))[0]
            for l_id in layer_ids
        }]
    else:
        layers = [{
            l_id: list(document.layers_from_ids([l_id]))[0]
        } for l_id in layer_ids]

    if reverse:
        for layer in layers:
            for key in layer:
                layer[key].reverse()

    for layer in layers:
        lines, removed_lines = _occult_layer(layer, tolerance, keep_occulted)

        for l_id, occulted_lines in lines.items():
            new_document.add(occulted_lines, layer_id=l_id)

        if keep_occulted and not removed_lines.is_empty():
            new_document.add(removed_lines, layer_id=removed_layer_id)

    return new_document
Exemplo n.º 30
0
def write(
    document: vp.Document,
    cmd_string: Optional[str],
    output,
    file_format: str,
    page_size: str,
    landscape: bool,
    center: bool,
    layer_label: str,
    pen_up: bool,
    color_mode: str,
    device: Optional[str],
    velocity: Optional[int],
    quiet: bool,
    single_path: bool,
):
    """Write command."""

    if document.is_empty():
        logging.warning("no geometry to save, no file created")
        return document

    if file_format is None:
        # infer format
        _, ext = os.path.splitext(output.name)
        file_format = ext.lstrip(".").lower()

    if file_format == "svg":
        page_size_px = None
        if page_size:
            page_size_px = vp.convert_page_size(page_size)
            if landscape:
                page_size_px = page_size_px[::-1]

        vp.write_svg(
            output=output,
            document=document,
            page_size=page_size_px,
            center=center,
            source_string=cmd_string if cmd_string is not None else "",
            layer_label_format=layer_label,
            show_pen_up=pen_up,
            color_mode=color_mode,
            single_path=single_path,
        )
    elif file_format == "hpgl":
        if not page_size:
            config = vp.config_manager.get_plotter_config(device)
            if config is not None:
                paper_config = config.paper_config_from_size(
                    document.page_size)
            else:
                paper_config = None

            if paper_config and document.page_size is not None:
                page_size = paper_config.name
                landscape = document.page_size[0] > document.page_size[1]
            else:
                logging.error(
                    "write: the plotter page size could not be inferred from the current page "
                    "size (use the `--page-size SIZE` option)")
                return document

        vp.write_hpgl(
            output=output,
            document=document,
            landscape=landscape,
            center=center,
            device=device,
            page_size=page_size,
            velocity=velocity,
            quiet=quiet,
        )
    else:
        logging.warning(
            f"write: format could not be inferred or format unknown '{file_format}', "
            "no file created (use the `--format` option)")

    return document