Exemple #1
0
def write(
    vector_data: VectorData,
    cmd_string: str,
    output,
    single_path: bool,
    page_format: Tuple[float, float],
    landscape: bool,
    center: bool,
    layer_label: str,
):
    """Write command."""

    if vector_data.is_empty():
        logging.warning("no geometry to save, no file created")
    else:
        if landscape:
            page_format = page_format[::-1]

        write_svg(
            output=output,
            vector_data=vector_data,
            page_format=page_format,
            center=center,
            source_string=cmd_string,
            layer_label_format=layer_label,
            single_path=single_path,
        )

    return vector_data
Exemple #2
0
def scale(
    vector_data: VectorData,
    scale: Tuple[float, float],
    layer: Union[int, List[int]],
    absolute: bool,
    keep_proportions: bool,
    origin_coords: Tuple[float, float],
):
    """Scale the geometries.

    The origin used is the bounding box center, unless the `--origin` option is used.

    By default, the arguments are used as relative factors (e.g. `scale 2 2` make the
    geometries twice as big in both dimensions). With `--to`, the arguments are interpreted as
    the final size. In this case, arguments understand the supported units (e.g.
    `scale --to 10cm 10cm`).

    By default, act 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.
    """

    if vector_data.is_empty():
        return vector_data

    # these are the layers we want to act on
    layer_ids = multiple_to_layer_ids(layer, vector_data)
    bounds = vector_data.bounds(layer_ids)

    if absolute:
        factors = (scale[0] / (bounds[2] - bounds[0]), scale[1] / (bounds[3] - bounds[1]))

        if keep_proportions:
            factors = (min(factors), min(factors))
    else:
        factors = scale

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

    logging.info(f"scaling factors: {factors}, origin: {origin}")

    for vid in layer_ids:
        lc = vector_data[vid]
        lc.translate(-origin[0], -origin[1])
        lc.scale(factors[0], factors[1])
        lc.translate(origin[0], origin[1])

    return vector_data
Exemple #3
0
def rotate(
    vector_data: VectorData,
    angle: float,
    layer: Union[int, List[int]],
    radian: bool,
    origin_coords: Tuple[float, float],
):
    """
    Rotate the geometries (clockwise positive).

    The origin used is the bounding box center, unless the `--origin` option is used.

    By default, act 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.
    """
    if vector_data.is_empty():
        return vector_data

    if not radian:
        angle *= math.pi / 180.0

    # these are the layers we want to act on
    layer_ids = multiple_to_layer_ids(layer, vector_data)

    bounds = vector_data.bounds(layer_ids)
    if len(origin_coords) == 2:
        origin = origin_coords
    else:
        origin = (
            0.5 * (bounds[0] + bounds[2]),
            0.5 * (bounds[1] + bounds[3]),
        )

    logging.info(f"rotating origin: {origin}")

    for vid in layer_ids:
        lc = vector_data[vid]
        lc.translate(-origin[0], -origin[1])
        lc.rotate(angle)
        lc.translate(origin[0], origin[1])

    return vector_data
Exemple #4
0
def skew(
    vector_data: VectorData,
    layer: Union[int, List[int]],
    angles: Tuple[float, float],
    radian: bool,
    origin_coords: Tuple[float, float],
):
    """
    Skew the geometries.

    The geometries are sheared by the provided angles along X and Y dimensions.

    The origin used in the bounding box center, unless the `--centroid` or `--origin` options
    are used.
    """
    if vector_data.is_empty():
        return vector_data

    # these are the layers we want to act on
    layer_ids = multiple_to_layer_ids(layer, vector_data)

    bounds = vector_data.bounds(layer_ids)
    if len(origin_coords) == 2:
        origin = origin_coords
    else:
        origin = (
            0.5 * (bounds[0] + bounds[2]),
            0.5 * (bounds[1] + bounds[3]),
        )

    if not radian:
        angles = tuple(a * math.pi / 180.0 for a in angles)

    logging.info(f"skewing origin: {origin}")

    for vid in layer_ids:
        lc = vector_data[vid]
        lc.translate(-origin[0], -origin[1])
        lc.skew(angles[0], angles[1])
        lc.translate(origin[0], origin[1])

    return vector_data
Exemple #5
0
def dbsample(vector_data: VectorData):
    """
    Show statistics on the current geometries in JSON format.
    """
    global debug_data

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

    debug_data.append(data)
    return vector_data
Exemple #6
0
def write(
    vector_data: VectorData,
    output,
    single_path: bool,
    page_format: str,
    landscape: bool,
    center: bool,
):
    """
    Save geometries to a SVG file.

    By default, the SVG generated has bounds tightly fit around the geometries. Optionally,
    a page format can be provided (`--page-format`). In this case, the geometries are not
    scaled or translated by default, even if they lie outside of the page bounds. The
    `--center` option translates the geometries to the center of the page.

    If output path is `-`, SVG content is output on stdout.
    """

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

    # compute bounds
    bounds = vector_data.bounds()
    if page_format != "tight":
        size = tuple(c * 96.0 / 25.4 for c in PAGE_FORMATS[page_format])
        if landscape:
            size = tuple(reversed(size))
    else:
        size = (bounds[2] - bounds[0], bounds[3] - bounds[1])

    if center:
        corrected_vector_data = copy.deepcopy(vector_data)
        corrected_vector_data.translate(
            (size[0] - (bounds[2] - bounds[0])) / 2.0 - bounds[0],
            (size[1] - (bounds[3] - bounds[1])) / 2.0 - bounds[1],
        )
    elif page_format == "tight":
        corrected_vector_data = copy.deepcopy(vector_data)
        corrected_vector_data.translate(-bounds[0], -bounds[1])
    else:
        corrected_vector_data = vector_data

    # output SVG
    dwg = svgwrite.Drawing(size=size, profile="tiny", debug=False)
    dwg.attribs["xmlns:inkscape"] = "http://www.inkscape.org/namespaces/inkscape"
    for layer_id in sorted(corrected_vector_data.layers.keys()):
        layer = corrected_vector_data.layers[layer_id]

        group = dwg.g(
            style="display:inline", id=f"layer{layer_id}", fill="none", stroke="black"
        )
        group.attribs["inkscape:groupmode"] = "layer"
        group.attribs["inkscape:label"] = str(layer_id)

        if single_path:
            group.add(
                dwg.path(
                    " ".join(
                        ("M" + " L".join(f"{x},{y}" for x, y in as_vector(line)))
                        for line in layer
                    ),
                )
            )
        else:
            for line in layer:
                group.add(dwg.path("M" + " L".join(f"{x},{y}" for x, y in as_vector(line)),))

        dwg.add(group)

    dwg.write(output, pretty=True)
    return vector_data
Exemple #7
0
def write(
    vector_data: VectorData,
    cmd_string: Optional[str],
    output,
    file_format: str,
    page_format: str,
    landscape: bool,
    center: bool,
    layer_label: str,
    pen_up: bool,
    color_mode: str,
    single_path: bool,
    device: Optional[str],
    velocity: Optional[int],
    quiet: bool,
):
    """Write command."""

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

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

    if file_format == "svg":
        page_format_px = convert_page_format(page_format)

        if landscape:
            page_format_px = page_format_px[::-1]

        write_svg(
            output=output,
            vector_data=vector_data,
            page_format=page_format_px,
            center=center,
            source_string=cmd_string if cmd_string is not None else "",
            layer_label_format=layer_label,
            single_path=single_path,
            show_pen_up=pen_up,
            color_mode=color_mode,
        )
    elif file_format == "hpgl":
        write_hpgl(
            output=output,
            vector_data=vector_data,
            landscape=landscape,
            center=center,
            device=device,
            page_format=page_format,
            velocity=velocity,
            quiet=quiet,
        )
    else:
        logging.warning(
            f"write: format could not be inferred or format unknown '{file_format}', "
            "no file created"
        )

    return vector_data