示例#1
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
示例#2
0
文件: style.py 项目: swatisrs/vsketch
def stylize_path(line: np.ndarray, weight: int, pen_width: float,
                 detail: float) -> vp.LineCollection:
    """Implement a heavy stroke weight by buffering multiple times the base path.

    Note: recursive buffering is to be avoided to properly control detail!
    """

    if weight == 1:
        return vp.LineCollection([line])

    lc = vp.LineCollection()

    # path to be used as starting point for buffering
    geom = LineString(vp.as_vector(line))
    if weight % 2 == 0:
        radius = pen_width / 2
        _add_to_line_collection(
            geom.buffer(radius,
                        resolution=_calc_buffer_resolution(radius, detail)),
            lc)
    else:
        radius = 0.0
        _add_to_line_collection(geom, lc)

    for i in range((weight - 1) // 2):
        radius += pen_width
        p = geom.buffer(radius,
                        resolution=_calc_buffer_resolution(radius, detail))
        _add_to_line_collection(p, lc)

    return lc
示例#3
0
    def _build_buffers(lc: vp.LineCollection) -> Tuple[np.ndarray, np.ndarray]:
        # build index array
        ranges: List[Sequence[int]] = []
        block = []
        cur_index = 0
        restart_mark = [-1]
        for line in lc:
            ranges.append(range(cur_index, cur_index + len(line)))
            ranges.append(restart_mark)
            cur_index += len(line)

            block.append(vp.as_vector(line))

        return np.vstack(block), np.concatenate(ranges)
示例#4
0
    def _build_buffers(
        lc: vp.LineCollection, color: ColorType = (0.0, 0.0, 0.0, 1.0)
    ) -> Tuple[np.ndarray, np.ndarray]:
        # build index array
        ranges: List[Sequence[int]] = []
        block = []
        cur_index = 0
        restart_mark = [-1]
        for line in lc:
            ranges.append(range(cur_index, cur_index + len(line)))
            ranges.append(restart_mark)
            cur_index += len(line)

            block.append([vp.as_vector(line), np.tile(color, (len(line), 1))])

        return np.block(block), np.concatenate(ranges)
示例#5
0
    def _build_buffers(cls, lc: vp.LineCollection) -> Tuple[np.ndarray, np.ndarray]:
        # build index array
        ranges: List[Sequence[int]] = []
        block = []
        cur_index = 0
        restart_mark = [-1]
        for i, line in enumerate(lc):
            n = len(line)
            ranges.append(range(cur_index, cur_index + n))
            ranges.append(restart_mark)
            cur_index += n

            color = cls.COLORS[i % len(cls.COLORS)]
            colors = np.tile(color, (n, 1))
            colors[::2, 0:3] *= 0.6

            block.append([vp.as_vector(line), colors])

        return np.block(block), np.concatenate(ranges)
示例#6
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
示例#7
0
    def _build_buffers(lc: vp.LineCollection):
        """Prepare the buffers for multi-polyline rendering. Closed polyline must have their
        last point identical to their first point."""

        indices = []
        reset_index = [-1]
        start_index = 0
        for i, line in enumerate(lc):
            if line[0] == line[-1]:  # closed path
                idx = np.arange(len(line) + 3) - 1
                idx[0], idx[-2], idx[-1] = len(line) - 1, 0, 1
            else:
                idx = np.arange(len(line) + 2) - 1
                idx[0], idx[-1] = 0, len(line) - 1

            indices.append(idx + start_index)
            start_index += len(line)
            indices.append(reset_index)

        return (
            np.vstack([vp.as_vector(line).astype("f4") for line in lc]),
            np.concatenate(indices).astype("i4"),
        )
示例#8
0
def show(
    vector_data: VectorData,
    show_axes: bool,
    show_grid: bool,
    show_pen_up: bool,
    show_points: bool,
    hide_legend: bool,
    colorful: bool,
    unit: str,
):
    """
    Display the geometry using matplotlib.

    By default, only the geometries are displayed without the axis. All geometries are
    displayed with black. When using the `--colorful` flag, each segment will have a different
    color (default matplotlib behaviour). This can be useful for debugging purposes.
    """

    scale = 1 / convert(unit)

    fig = plt.figure()
    color_idx = 0
    collections = {}
    for layer_id, lc in vector_data.layers.items():
        if colorful:
            color = COLORS[color_idx:] + COLORS[:color_idx]
            marker_color = "k"
            color_idx += len(lc)
        else:
            color = COLORS[color_idx]  # type: ignore
            marker_color = [color]  # type: ignore
            color_idx += 1
        if color_idx >= len(COLORS):
            color_idx = color_idx % len(COLORS)

        layer_lines = matplotlib.collections.LineCollection(
            (as_vector(line) * scale for line in lc),
            color=color,
            lw=1,
            alpha=0.5,
            label=str(layer_id),
        )
        collections[layer_id] = [layer_lines]
        plt.gca().add_collection(layer_lines)

        if show_points:
            points = np.hstack([line for line in lc]) * scale
            layer_points = plt.gca().scatter(points.real,
                                             points.imag,
                                             marker=".",
                                             c=marker_color,
                                             s=16)
            collections[layer_id].append(layer_points)

        if show_pen_up:
            pen_up_lines = matplotlib.collections.LineCollection(
                ((as_vector(lc[i])[-1] * scale,
                  as_vector(lc[i + 1])[0] * scale)
                 for i in range(len(lc) - 1)),
                color=(0, 0, 0),
                lw=0.5,
                alpha=0.5,
            )
            collections[layer_id].append(pen_up_lines)
            plt.gca().add_collection(pen_up_lines)

    plt.gca().invert_yaxis()
    plt.axis("equal")
    plt.margins(0, 0)

    if not hide_legend:
        lgd = plt.legend()
        # we will set up a dict mapping legend line to orig line, and enable
        # picking on the legend line
        line_dict = {}
        for lgd_line, lgd_text in zip(lgd.get_lines(), lgd.get_texts()):
            lgd_line.set_picker(5)  # 5 pts tolerance
            layer_id = int(lgd_text.get_text())
            if layer_id in collections:
                line_dict[lgd_line] = collections[layer_id]

        def on_pick(event):
            line = event.artist
            vis = not line_dict[line][0].get_visible()
            for ln in line_dict[line]:
                ln.set_visible(vis)

            if vis:
                line.set_alpha(1.0)
            else:
                line.set_alpha(0.2)
            fig.canvas.draw()

        fig.canvas.mpl_connect("pick_event", on_pick)

    if show_axes or show_grid:
        plt.axis("on")
        plt.xlabel(f"[{unit}]")
        plt.ylabel(f"[{unit}]")
    else:
        plt.axis("off")
    if show_grid:
        plt.grid("on")
    plt.show()

    return vector_data
示例#9
0
def display_matplotlib(
    vector_data: Union[vp.LineCollection, vp.VectorData],
    page_format: Tuple[float, float] = None,
    center: bool = False,
    show_axes: bool = True,
    show_grid: bool = False,
    show_pen_up: bool = False,
    colorful: bool = False,
    unit: str = "px",
    fig_size: Tuple[float, float] = None,
) -> None:
    if isinstance(vector_data, vp.LineCollection):
        vector_data = vp.VectorData(vector_data)

    scale = 1 / vp.convert(unit)

    if fig_size:
        plt.figure(figsize=fig_size)
    plt.cla()

    # draw page
    if page_format is not None:
        w = page_format[0] * scale
        h = page_format[1] * scale
        dw = 10 * scale
        plt.fill(
            np.array([w, w + dw, w + dw, dw, dw, w]),
            np.array([dw, dw, h + dw, h + dw, h, h]),
            "k",
            alpha=0.3,
        )
        plt.plot(
            np.array([0, 1, 1, 0, 0]) * w,
            np.array([0, 0, 1, 1, 0]) * h,
            "-k",
            lw=0.25,
        )

    # compute offset
    offset = complex(0, 0)
    if center and page_format:
        bounds = vector_data.bounds()
        if bounds is not None:
            offset = complex(
                (page_format[0] - (bounds[2] - bounds[0])) / 2.0 - bounds[0],
                (page_format[1] - (bounds[3] - bounds[1])) / 2.0 - bounds[1],
            )
    offset_ndarr = np.array([offset.real, offset.imag])

    # plot all layers
    color_idx = 0
    collections = {}
    for layer_id, lc in vector_data.layers.items():
        if colorful:
            color: Union[Tuple[float, float, float], List[Tuple[
                float, float,
                float]]] = COLORS[color_idx:] + COLORS[:color_idx]
            color_idx += len(lc)
        else:
            color = COLORS[color_idx]
            color_idx += 1
        if color_idx >= len(COLORS):
            color_idx = color_idx % len(COLORS)

        # noinspection PyUnresolvedReferences
        layer_lines = matplotlib.collections.LineCollection(
            (vp.as_vector(line + offset) * scale for line in lc),
            color=color,
            lw=1,
            alpha=0.5,
            label=str(layer_id),
        )
        collections[layer_id] = [layer_lines]
        plt.gca().add_collection(layer_lines)

        if show_pen_up:
            # noinspection PyUnresolvedReferences
            pen_up_lines = matplotlib.collections.LineCollection(
                ((
                    (vp.as_vector(lc[i])[-1] + offset_ndarr) * scale,
                    (vp.as_vector(lc[i + 1])[0] + offset_ndarr) * scale,
                ) for i in range(len(lc) - 1)),
                color=(0, 0, 0),
                lw=0.5,
                alpha=0.5,
            )
            collections[layer_id].append(pen_up_lines)
            plt.gca().add_collection(pen_up_lines)

    plt.gca().invert_yaxis()
    plt.axis("equal")
    plt.margins(0, 0)

    if show_axes or show_grid:
        plt.axis("on")
        plt.xlabel(f"[{unit}]")
        plt.ylabel(f"[{unit}]")
    else:
        plt.axis("off")
    if show_grid:
        plt.grid("on")

    plt.show()
示例#10
0
文件: write.py 项目: zxsq-cc/vpype
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
示例#11
0
def _show_mpl(
    document: vp.Document,
    show_axes: bool,
    show_grid: bool,
    show_pen_up: bool,
    show_points: bool,
    hide_legend: bool,
    colorful: bool,
    unit: str,
):
    """Display the geometry using matplotlib.

    By default, only the geometries are displayed without the axis. All geometries are
    displayed with black. When using the `--colorful` flag, each segment will have a different
    color (default matplotlib behaviour). This can be useful for debugging purposes.
    """

    # deferred import to optimise startup time
    import matplotlib.collections
    import matplotlib.pyplot as plt

    scale = 1 / vp.convert_length(unit)

    fig = plt.figure()
    color_idx = 0
    collections = {}

    # draw page boundaries
    if document.page_size:
        w = document.page_size[0] * scale
        h = document.page_size[1] * scale
        dw = 10 * scale
        plt.plot(
            np.array([0, 1, 1, 0, 0]) * w,
            np.array([0, 0, 1, 1, 0]) * h,
            "-k",
            lw=0.25,
            label=None,
        )
        plt.fill(
            np.array([w, w + dw, w + dw, dw, dw, w]),
            np.array([dw, dw, h + dw, h + dw, h, h]),
            "k",
            alpha=0.3,
            label=None,
        )

    for layer_id, lc in document.layers.items():
        if colorful:
            color = COLORS[color_idx:] + COLORS[:color_idx]
            marker_color = "k"
            color_idx += len(lc)
        else:
            color = COLORS[color_idx]  # type: ignore
            marker_color = [color]  # type: ignore
            color_idx += 1
        if color_idx >= len(COLORS):
            color_idx = color_idx % len(COLORS)

        layer_lines = matplotlib.collections.LineCollection(
            (vp.as_vector(line) * scale for line in lc),
            color=color,
            lw=1,
            alpha=0.5,
            label=str(layer_id),
        )
        collections[layer_id] = [layer_lines]
        plt.gca().add_collection(layer_lines)

        if show_points:
            points = np.hstack([line for line in lc]) * scale
            layer_points = plt.gca().scatter(points.real,
                                             points.imag,
                                             marker=".",
                                             c=marker_color,
                                             s=16)
            collections[layer_id].append(layer_points)

        if show_pen_up:
            pen_up_lines = matplotlib.collections.LineCollection(
                ((vp.as_vector(lc[i])[-1] * scale,
                  vp.as_vector(lc[i + 1])[0] * scale)
                 for i in range(len(lc) - 1)),
                color=(0, 0, 0),
                lw=0.5,
                alpha=0.5,
            )
            collections[layer_id].append(pen_up_lines)
            plt.gca().add_collection(pen_up_lines)

    plt.gca().invert_yaxis()
    plt.axis("equal")
    plt.margins(0, 0)

    if not hide_legend:
        lgd = plt.legend(loc="upper right")
        # we will set up a dict mapping legend line to orig line, and enable
        # picking on the legend line
        line_dict = {}
        for lgd_line, lgd_text in zip(lgd.get_lines(), lgd.get_texts()):
            lgd_line.set_picker(True)  # 5 pts tolerance
            lgd_line.set_pickradius(5)
            layer_id = int(lgd_text.get_text())
            if layer_id in collections:
                line_dict[lgd_line] = collections[layer_id]

        def on_pick(event):
            line = event.artist
            vis = not line_dict[line][0].get_visible()
            for ln in line_dict[line]:
                ln.set_visible(vis)

            if vis:
                line.set_alpha(1.0)
            else:
                line.set_alpha(0.2)
            fig.canvas.draw()

        fig.canvas.mpl_connect("pick_event", on_pick)

    if show_axes or show_grid:
        plt.axis("on")
        plt.xlabel(f"[{unit}]")
        plt.ylabel(f"[{unit}]")
    else:
        plt.axis("off")
    if show_grid:
        plt.grid("on")
    plt.show()
示例#12
0
    def _replot(self):
        if not self._init_lims:
            old_lims = (self.ax.get_xlim(), self.ax.get_ylim())
        else:
            old_lims = None
            self._init_lims = False
        self.ax.cla()

        scale = 1 / vpype.convert(self._unit)

        # draw page
        w = self._page_format[0] * scale
        h = self._page_format[1] * scale
        dw = 10 * scale
        self.ax.plot(
            np.array([0, 1, 1, 0, 0]) * w,
            np.array([0, 0, 1, 1, 0]) * h,
            "-k",
            lw=0.25,
        )
        self.ax.fill(
            np.array([w, w + dw, w + dw, dw, dw, w]),
            np.array([dw, dw, h + dw, h + dw, h, h]),
            "k",
            alpha=0.3,
        )

        color_idx = 0
        for layer_id, lc in self._vector_data.layers.items():
            if self._colorful:
                color = COLORS[color_idx:] + COLORS[:color_idx]
                marker_color = "k"
                color_idx += len(lc)
                if color_idx >= len(COLORS):
                    color_idx = color_idx % len(COLORS)
            else:
                color = self._layers[layer_id].color
                marker_color = [color]

            layer_lines = matplotlib.collections.LineCollection(
                (vpype.as_vector(line) * scale for line in lc),
                color=color,
                lw=1,
                alpha=0.5,
                label=str(layer_id),
            )
            self._layers[layer_id].lines = [layer_lines]
            self.ax.add_collection(layer_lines)

            if self._show_points:
                points = np.hstack([line for line in lc]) * scale
                layer_points = self.ax.scatter(points.real,
                                               points.imag,
                                               marker=".",
                                               c=marker_color,
                                               s=16)
                self._layers[layer_id].lines.append(layer_points)

            if self._show_pen_up:
                pen_up_lines = matplotlib.collections.LineCollection(
                    ((
                        vpype.as_vector(lc[i])[-1] * scale,
                        vpype.as_vector(lc[i + 1])[0] * scale,
                    ) for i in range(len(lc) - 1)),
                    color=(0, 0, 0),
                    lw=0.5,
                    alpha=0.5,
                )
                self._layers[layer_id].lines.append(pen_up_lines)
                self.ax.add_collection(pen_up_lines)

        self.ax.invert_yaxis()
        self.ax.axis("equal")

        # set visibility
        for layer_spec in self._layers.values():
            if not layer_spec.visible:
                for ln in layer_spec.lines:
                    ln.set_visible(False)

        if self._show_axes or self._show_grid:
            self.ax.axis("on")
            self.ax.set_xlabel(f"[{self._unit}]")
            self.ax.set_ylabel(f"[{self._unit}]")
        else:
            self.ax.axis("off")
        if self._show_grid:
            self.ax.grid("on", alpha=0.2)

        if old_lims is not None:
            self.ax.set_xlim(old_lims[0])
            self.ax.set_ylim(old_lims[1])
        else:
            self.toolbar.update()

        for text in self.ax.get_xticklabels():
            text.set_horizontalalignment("center")
            text.set_verticalalignment("bottom")

        for text in self.ax.get_yticklabels():
            text.set_horizontalalignment("left")
            text.set_verticalalignment("center")
        self.canvas.draw()
示例#13
0
def circlecrop(lines: vp.LineCollection, x: float, y: float, r: float, quantization: float):
    """Crop to a circular area."""

    circle = Polygon(vp.as_vector(vp.circle(x, y, r, quantization)))
    mls = lines.as_mls()
    return vp.LineCollection(mls.intersection(circle))