def get_paths(file_path): doc = Document(file_path) paths = doc.paths() paths = [ path for path in paths if path.length() > args.threshold_path_length ] return paths
def svgpathtools_flatten(stage_svg): # TODO: perhaps use this instead of inkscapes's deep ungroup? from svgpathtools import Document doc = Document(str(stage_svg)) results = doc.flatten_all_paths() for result in results: # TODO: save result.path to new SVG document # and overwrite stage_svg? pass
def _create_from_svg(cls, filepath, height_mm): assert isinstance(filepath, pathlib.Path) if not filepath.is_file(): raise IOError( "Unable to create Face from image file: {}. File does not exist" .format(filepath)) # Load as a document rather than as paths directly (using svg2paths) because # the document respects any transforms doc = Document(str(filepath)) paths = doc.paths() continuous_paths = cls._get_continuous_subpaths(paths) continuous_paths = cls._remove_zero_length_lines(continuous_paths) ymin = min([path.bbox()[2] for path in continuous_paths]) ymax = max([path.bbox()[3] for path in continuous_paths]) current_height = ymax - ymin assert current_height >= 0 scaling_factor = height_mm / current_height scaled_paths = [ path.scaled(scaling_factor, -scaling_factor) for path in continuous_paths ] # Line up to the x and y axes xmin = min([path.bbox()[0] for path in scaled_paths]) ymin = min([path.bbox()[2] for path in scaled_paths]) translated_paths = [ path.translated(complex(-xmin, -ymin)) for path in scaled_paths ] normalized_paths = cls._normalize_paths_clockwise(translated_paths) path_hierarchies = cls._create_path_hierarchy(normalized_paths) # Currently only really support a single main contiguous shape with holes. # Although multiple disconnected shapes can be generated, they won't be # perfectly represented by the final geometry because some material has to # connect them assert len(path_hierarchies) == 1 faceMaker = BRepBuilderAPI_MakeFace(PL_XZ) for path_hierarchy in path_hierarchies: root_edges = cls._create_edges_from_path( path_hierarchy.root_path()) root_wire = cls._create_wire_from_edges(root_edges) faceMaker.Add(root_wire) for sub_path in path_hierarchy.child_paths(): sub_path_edges = cls._create_edges_from_path(sub_path) sub_path_wire = cls._create_wire_from_edges(sub_path_edges) # reverse the wire so it creates a hole sub_path_wire.Reverse() faceMaker.Add(sub_path_wire) return faceMaker.Shape()
def read(file, quantization: float) -> LineCollection: """ Extract geometries from a SVG file. This command only extracts path elements as well as primitives (rectangles, ellipses, lines, polylines, polygons). In particular, text and bitmap images are discarded, as well as 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 (1mm by default). """ doc = Document(file) results = doc.flatten_all_paths() root = doc.tree.getroot() # we must interpret correctly the viewBox, width and height attribs in order to scale # the file content to proper pixels if "viewBox" in root.attrib: # A view box is defined so we must correctly scale from user coordinates # https://css-tricks.com/scale-svg/ # TODO: we should honor the `preserveAspectRatio` attribute viewbox_min_x, viewbox_min_y, viewbox_width, viewbox_height = [ float(s) for s in root.attrib["viewBox"].split() ] w = convert(root.attrib.get("width", viewbox_width)) h = convert(root.attrib.get("height", viewbox_height)) scale_x = w / viewbox_width scale_y = h / viewbox_height offset_x = -viewbox_min_x offset_y = -viewbox_min_y else: scale_x = 1 scale_y = 1 offset_x = 0 offset_y = 0 lc = LineCollection() for result in results: for elem in result.path: if isinstance(elem, Line): coords = np.array([elem.start, elem.end]) else: # This is a curved element that we approximate with small segments step = int(math.ceil(elem.length() / quantization)) coords = np.empty(step + 1, dtype=complex) coords[0] = elem.start for i in range(step - 1): coords[i + 1] = elem.point((i + 1) / step) coords[-1] = elem.end # transform coords += offset_x + 1j * offset_y coords.real *= scale_x coords.imag *= scale_y lc.append(coords) return lc
def arc_length_statistics(path_to_svg): ''' Given: path_to_svg: A path to an SVG file. Normalizes by the svg's long edge as defined by its viewBox. Ignores <svg> width or height attributes. ''' flatpaths = None paths = None try: doc = Document(path_to_svg) flatpaths = doc.flatten_all_paths() except: paths, _ = svg2paths(path_to_svg) flatpaths = paths ## Absolute distances lengths = [] for path in flatpaths: total_path_length = 0. last_pt = None # if this is get by flatten_all_paths, then we just need to get the first item if paths == None: path = path[0] for seg in path: ## Make sure this segment is connected to the previous one. ## If not, start a new one. ## I checked and it doesn't look like svgpathtools tells us when a Move ## command happens, so we have to figure it out. if not (last_pt is None or allclose(last_pt, seg.point(0))): lengths.append(total_path_length) total_path_length = 0. ## Add the current segment to the running tally. total_path_length += seg.length() last_pt = seg.point(1) lengths.append(total_path_length) ## Divide by long edge. if 'viewBox' in doc.root.attrib: import re _, _, width, height = [ float(v) for v in re.split('[ ,]+', doc.root.attrib['viewBox'].strip()) ] long_edge = max(width, height) print("Normalizing by long edge:", long_edge) lengths = [l / long_edge for l in lengths] elif "width" in doc.root.attrib and "height" in doc.root.attrib: width = doc.root.attrib["width"].strip().strip("px") height = doc.root.attrib["height"].strip().strip("px") long_edge = max(float(width), float(height)) print("Normalizing by long edge:", long_edge) lengths = [l / long_edge for l in lengths] else: print( "WARNING: No viewBox found in <svg>. Not normalizing by long edge." ) print("Done") return lengths