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
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)
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
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
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
def _all_document_ops(doc: Document): doc.bounds() doc.length() doc.segment_count()
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)
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
def display_matplotlib( document: vp.Document, page_size: 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: scale = 1 / vp.convert_length(unit) if fig_size: plt.figure(figsize=fig_size) plt.cla() # draw page if page_size is not None: w = page_size[0] * scale h = page_size[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_size: bounds = document.bounds() if bounds is not None: offset = complex( (page_size[0] - (bounds[2] - bounds[0])) / 2.0 - bounds[0], (page_size[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 document.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()
def display_ipython( document: vp.Document, page_size: Optional[Tuple[float, float]], center: bool = False, show_pen_up: bool = False, color_mode: str = "layer", ) -> None: """Implements a SVG previsualisation with pan/zoom support for IPython. If page_size is provided, a page is displayed and the sketch is laid out on it. Otherwise the sketch is displayed using its intrinsic boundaries. """ if "IPython" not in sys.modules: raise RuntimeError("IPython display cannot be used outside of IPython") svg_io = io.StringIO() vp.write_svg( svg_io, document, page_size if page_size is not None else (0, 0), center, show_pen_up=show_pen_up, color_mode=color_mode, ) MARGIN = 10 if page_size is None: bounds = document.bounds() if bounds: svg_width = bounds[2] - bounds[0] svg_height = bounds[3] - bounds[1] else: svg_width = 0 svg_height = 0 else: svg_width = page_size[0] svg_height = page_size[1] page_boundaries = f""" <polygon points="{svg_width},{MARGIN} {svg_width + MARGIN},{MARGIN} {svg_width + MARGIN},{svg_height + MARGIN} {MARGIN},{svg_height + MARGIN} {MARGIN},{svg_height} {svg_width},{svg_height}" style="fill:black;stroke:none;opacity:0.3;" /> <rect width="{svg_width}" height="{svg_height}" style="fill:none;stroke-width:1;stroke:rgb(0,0,0)" /> """ svg_margin = MARGIN if page_size is not None else 0 svg_id = f"svg_display_{random.randint(0, 10000)}" IPython.display.display_html( f"""<div id="container" style="width: 80%; height: {svg_height + svg_margin}px;"> <svg id="{svg_id}" width="{svg_width + svg_margin}px" height={svg_height + svg_margin} viewBox="0 0 {svg_width + svg_margin} {svg_height + svg_margin}"> {page_boundaries if page_size is not None else ""} {svg_io.getvalue()} </svg> </div> <script src="{get_svg_pan_zoom_url()}"></script> <script> svgPanZoom('#{svg_id}', {{ zoomEnabled: true, controlIconsEnabled: true, center: true, zoomScaleSensitivity: 0.3, contain: true, }}); </script> """, raw=True, )