def setUpClass(cls): """Initialize test cases for Quadratic Bezier class.""" cls.name = 'quadractic_bezier' # Initialize nominal cases for Quadratic Bezier class cls.paths = [ parse_path("m 25, 47 q 17, -7 6, 20"), parse_path("m 48, 68 q 14 -57 26, 0"), parse_path("m 85, 59 q 14, 57 26, 0"), parse_path("m 128, 64 q 14, 17 25, -18"), parse_path("m 128, 64 q 14, 17 25, -18"), parse_path("m 81, 29 q 14, -17 25, 18"), ] # Initialize edge cases for Quadratic Bezier class cls.edge_paths = [ parse_path("m 80, 20 q 10,10 -10, 20"), # parse_path("m 80, 20 q 0, 0 0, 0"), # Single point # parse_path("m 80, 20 q 10, 0 20, 0"), # Straight line parse_path("m 80, 20 q 10,10 0, 20"), ] # ym_list for specific yms cls.ym_list = [None, 50, 80, None, None, None, None, None] # visual control all correct cls.results.update({ "quadractic_bezier_1": [2, 1, 1, [[(34.25259515570934 + 46.99999999999999j)], [], [(34.425982139631 + 57j)]]], "quadractic_bezier_2": [2, 2, 2, [[(74 + 68j)], [(48 + 68j)], [(69.2064901963537 + 50j), (53.42508875101472 + 49.99999999999999j)]]], "quadractic_bezier_3": [2, 2, 2, [[(111 + 59j)], [(85 + 59j)], [(105.03728034118508 + 80j), (91.69956176407807 + 80j)]]], "quadractic_bezier_4": [2, 1, 1, [[(145.0251479289941 + 64j)], [], [(149.7705100077186 + 55j)]]], "quadractic_bezier_5": [2, 1, 1, [[(145.0251479289941 + 64j)], [], [(149.7705100077186 + 55j)]]], "quadractic_bezier_6": [2, 1, 1, [[(98.02514792899407 + 29j)], [], [(102.77051000771863 + 38j)]]], "quadractic_bezier_7": [1, 1, 1, [[], [], [(82.5 + 30j)]]], "quadractic_bezier_8": [1, 1, 1, [[], [], [(85 + 30j)]]], })
def setUpClass(cls): """Initialize test cases for Arc class.""" # configure_pathPoint_class() cls.name = 'arc' # Initialize cases for Arc class for flag in ['0 0', '0 1', '1 0', '1 1']: cls.paths.extend([ parse_path(f"M 50, 200 a 100, 50 0 {flag} 250,50"), parse_path(f"M 400, 100 a 100, 50 30 {flag} 250,50"), parse_path(f"M 400, 300 a 100, 50 45 {flag} 22,244"), parse_path(f"M 750, 200 a 100, 50 135 {flag} 22,244"), parse_path(f"M 750, 300 a 100, 50 220 {flag} 22,244"), parse_path(f"M 950, 100 a 100, 50 310 {flag} 22,244"), ]) # Initialize edge cases for Arc class cls.edge_paths.extend([ parse_path("M 400 200 a100 50, 0, 0, 0, 0 250"), parse_path("M 50 200 a100 50, 0, 0, 1, 0 250"), parse_path("M 400 200 a100 50, 90, 0, 0, 0 250"), ]) # visual control of results show that most results are wrong if the arc has a rotation cls.results.update({ "i1_arc_0_0_0": [1, 2, 1, [[], [(49.99999999999997 + 249.99999999999997j)], [(40.3708798216374 + 225j)]]], "i2_arc_30_0_0": [1, 2, 1, [[], [(437.1153753760908 + 149.9999999999999j)], []]], "i3_arc_45_0_0": [2, 1, 1, [[], [(275.59999999999997 + 300j)], [(295.26495776991305 + 421.999999999)]]], "i4_arc_135_0_0": [1, 2, 1, [[], [(603.5999999999999 + 443.9999999999994j)], [(632.0992629966765 + 321.9999999999999j)]]], "i5_arc_220_0_0": [2, 1, 1, [[(611.0555150486008 + 300j)], [], [(631.783782028338 + 421.9999999999999j)]]], "i6_arc_310_0_0": [1, 2, 1, [[], [(819.4282706707221 + 344.0000000000003j)], [(844.2419419089059 + 222.00000000000006j)]]], "i7_arc_0_0_1": [2, 1, 1, [[(300 + 200j)], [], [(309.6291201783626 + 225j)]]], "i8_arc_30_0_1": [2, 1, 1, [[(612.8846246239091 + 99.99999999999994j)], [], [(635.2102190245106 + 124.99999999999997j)]]], "i9_arc_45_0_1": [1, 2, 1, [[], [(546.3999999999999 + 544.0000000000002j)], [(526.735042230087 + 421.99999999999994j)]]], "i10_arc_135_0_1": [2, 1, 1, [[(918.4000000000001 + 200.00000000000034j)], [], [(889.9007370033237 + 321.99999999999994j), ]]], "i11_arc_220_0_1": [1, 2, 1, [[], [(910.9444849513991 + 544.0000000000001j)], [(890.2162179716619 + 421.99999999999994j), (631.783782028338 + 421.9999999999999j)]]], "i12_arc_310_0_1": [2, 1, 1, [[(1102.5717293292782 + 100.00000000000063j)], [(819.4282706707221 + 344.0000000000003j)], [(1077.758058091094 + 222.00000000000006j)]]], "i13_arc_0_1_0": [1, 2, 1, [[], [(49.99999999999997 + 249.99999999999997j)], [(40.3708798216374 + 225j)]]], "i14_arc_30_1_0": [1, 2, 1, [[], [(437.11537351971816 + 150.00000000000006j)], [(414.789780047303 + 124.99999999999993j)]]], "i15_arc_45_1_0": [2, 1, 1, [[(275.59999999999997 + 300j)], [], [(295.26495776991305 + 421.9999999999999j)]]], "i16_arc_135_1_0": [1, 2, 1, [[], [(603.5999999999999 + 443.9999999999994j)], [(632.0992629966765 + 321.9999999999999j)]]], "i17_arc_220_1_0": [2, 1, 1, [[(611.0555150486008 + 300j)], [], [(631.783782028338 + 421.9999999999999j)]]], "i18_arc_310_1_0": [1, 2, 1, [[], [(819.4282706707221 + 344.0000000000003j)], [(844.2419419089059 + 222.00000000000006j)]]], "i19_arc_0_1_1": [2, 1, 1, [[(300 + 200j)], [], [(309.6291201783626 + 225j)]]], "i20_arc_30_1_1": [2, 1, 1, [[(612.8846264802819 + 99.99999999999996j)], [], [(635.2102199526969 + 124.99999999999991j)]]], "i21_arc_45_1_1": [1, 2, 1, [[], [(546.3999999999999 + 544.0000000000002j)], [(526.735042230087 + 421.99999999999994j)]]], "i22_arc_135_1_1": [2, 1, 1, [[(918.4000000000001 + 200.00000000000034j)], [], [(889.9007370033237 + 321.99999999999994j)]]], "i23_arc_220_1_1": [1, 2, 1, [[], [(910.9444849513991 + 544.0000000000001j)], [(890.2162179716619 + 421.99999999999994j)]]], "i24_arc_310_1_1": [2, 1, 1, [[(1102.5717293292782 + 100.00000000000063j)], [], [(1077.758058091094 + 222.00000000000006j)]]], "i25_arc_0_0_0": [1, 1, 1, [[], [], [(150 + 325j)]]], "i26_arc_0_0_1": [1, 1, 1, [[], [], [(300 + 325j)]]], "i27_arc_90_0_0": [1, 1, 1, [[], [], [(337.5 + 325j)]]], })
def extractStrokes(path): bezpath = parse_path(path) n = 100 ptpath = [bezpath.point(t) for t in np.linspace(0, 1, n)] return [((ptpath[i % n].real, ptpath[i % n].imag), (ptpath[(i + 1) % n].real, ptpath[(i + 1) % n].imag)) for i in range(n)]
def __init__(self, node, layer): """ self.kind from the path label describes the type of control self._id from the path id is the enum name excluding _PARAM/INPUT/etc self.widget from the path title is the widget class name self.config is a python variable in the path description (dict) self.pos is [x,y] location of the center of the control """ self.kind = self.enum_kind_map[self.kind_map[layer.lower()]] self._id = node.get('id').upper() d = node.get('d') self.enum_base = None self.enum_idx = None self.enum_count = None self.path = parse_path(d) bbox = self.path.bbox() self.pos = [(bbox[i * 2] + bbox[i * 2 + 1]) / 2 for i in range(2)] self.widget = None self.config = {} for subnode in node.getchildren(): if Control.is_title(subnode): self.widget = subnode.text elif Control.is_desc(subnode): try: self.config = ast.literal_eval(subnode.text) except: print(f'Failed to load for {self._id} from {subnode.text}') raise
def setUpClass(cls): """Initialize test cases for Cubic Bezier class.""" cls.name = 'cubic_bezier' # Initialize nominal cases for Cubic Bezier class cls.paths = [ parse_path("m 10, 110 c 46,-16 21, 22 5, 24"), parse_path("m 70, 134 c 15, 7 51, -18 29, -32"), parse_path("m 139, 100 c 26,-15 18, 33 8, 44"), parse_path("m 35, 151 c 15, -7 51, 18 29, 32"), parse_path("m 133, 154 c -46,-16 -21, 22 -5, 24"), ] # Initialize edge cases for Cubic Bezier class cls.edge_paths = [ parse_path("m 139, 100 c 26, 5 18, 33 8, 44"), parse_path("m 35, 151 c 15, 0 51, 18 29, 32"), # parse_path("m 80, 20 c 10, 0 20, 0 30, 0"), this is a line ] # visual control cubic_bezier_1 and cubic_bezier_5 and cubic_bezier_7 are missing one cutpoint at the end cls.results.update({ "cubic_bezier_1": [2, 1, 1, [[(36.003610559732806 + 110j)], [], [(32.078141538112355 + 122.00000000000001j)]]], "cubic_bezier_2": [2, 1, 1, [[(83.69506360982835 + 134j)], [], [(104.62766766084235 + 118j)]]], "cubic_bezier_3": [2, 1, 1, [[(153.52855800284362 + 100j)], [], [(155.59972699011112 + 121.99999999999997j)]]], "cubic_bezier_4": [2, 1, 1, [[(48.69506360982835 + 151j)], [], [(69.62766766084235 + 167j)]]], "cubic_bezier_5": [2, 1, 1, [[(106.9963894402672 + 154j)], [], [(110.92185846188764 + 166j)]]], "cubic_bezier_6": [1, 1, 1, [[], [], [(156.4095736229877 + 122j)]]], "cubic_bezier_7": [1, 1, 1, [[], [], [(68.65030968920104 + 167j)]]], })
def setUpClass(cls): """Initialize test cases for Quadratic Bezier class.""" cls.name = 'quadractic_bezier' # Initialize nominal cases for Quadratic Bezier class cls.paths = [ parse_path("m 25, 47 q 17, -7 6, 20"), parse_path("m 48, 68 q 14 -17 26, 0"), parse_path("m 85, 59 q 14, 17 26, 0"), parse_path("m 128, 64 q 14, 17 25, -18"), parse_path("m 128, 64 q 14, 17 25, -18"), parse_path("m 81, 29 q 14, -17 25, 18"), ] # Initialize edge cases for Quadratic Bezier class cls.edge_paths = [ parse_path("m 80, 20 q 10,10 -10, 20"), parse_path("m 80, 20 q 0, 0 0, 0"), # Single point parse_path("m 80, 20 q 10, 0 20, 0"), # Straight line parse_path("m 80, 20 q 10,10 0, 20"), ]
def process_paths(path_groups): """ Extracts pose and region annotations represented as paths :param path_groups: a list of groups each containing a text element and a path :return: a tuple of poses and regions """ if len(path_groups) == 0: return [], [] regions = [] poses = [] for group in path_groups: if len(list(group)) != 2: # May want to print a warning here continue # We assume that the text was created in inkscape so the string will be in a tspan path, text = group.find( ".//{}".format(path_el)), get_text_from_group(group) if text is None: warn("No text label found for path group: {}".format(group)) continue name = text transform = get_transform(group) # Single line segment path => pose try: pose = extract_line_from_path(path, transform) poses.append(tuple([name] + list(pose))) continue except RuntimeError: pass # SVG paths are specified in a rich language of segment commands: # https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d # We'll use a new dependency to extract what we can path_geom = parse_path(path.attrib["d"]) # If they're all lines, let's assume it's closed and use it as a region if all(map(is_line, path_geom)): # Real part => x, imag part => y lines = map( lambda l: ((l.start.real, l.start.imag), (l.end.real, l.end.imag)), path_geom) # Each line segment starts where the previous ended, so we can drop the end points points = map(lambda l: l[0], lines) points = map(lambda p: (float_s3(p[0]), float_s3(p[1])), points) points = map(lambda p: apply_transform(p, transform), points) regions.append((name, points)) else: warn("Encountered path that couldn't be parsed {}".format(name)) return poses, regions
def setUpClass(cls): """Initialize test cases for Arc class.""" # configure_pathPoint_class() cls.name = 'arc' # Initialize nominal cases for Arc class for flag in ['0 0', '0 1', '1 0', '1 1']: cls.paths.extend([ parse_path(f"M 50, 200 a 100, 50 0 {flag} 250,50"), parse_path(f"M 400, 100 a 100, 50 30 {flag} 250,50"), parse_path(f"M 400, 300 a 100, 50 45 {flag} 22,244"), parse_path(f"M 750, 200 a 100, 50 135 {flag} 22,244"), parse_path(f"M 750, 300 a 100, 50 220 {flag} 22,244"), parse_path(f"M 950, 100 a 100, 50 310 {flag} 22,244"), ]) # Initialize edge cases for Arc class cls.edge_paths.extend([ parse_path("M 50 200 a100 50, 0, 0, 0, 0 250"), parse_path("M 50 200 a100 50, 0, 0, 1, 0 250"), parse_path("M 50 200 a100 50, 90, 0, 0, 0 250"), ])
def setUpClass(cls): """Initialize test cases for Cubic Bezier class.""" cls.name = 'cubic_bezier' # Initialize nominal cases for Cubic Bezier class cls.paths = [ parse_path("m 10, 110 c 46,-16 21, 22 5, 24"), parse_path("m 70, 134 c 15, 7 51, -18 29, -32"), parse_path("m 139, 100 c 26,-15 18, 33 8, 44"), parse_path("m 35, 151 c 15, -7 51, 18 29, 32"), parse_path("m 133, 154 c -46,-16 -21, 22 -5, 24"), ] # Initialize edge cases for Cubic Bezier class cls.edge_paths = [ parse_path("m 139, 100 c 26, 5 18, 33 8, 44"), parse_path("m 35, 151 c 15, 0 51, 18 29, 32"), parse_path("m 80, 20 c 10, 0 20, 0 30, 0"), ]
def recursively_parse_paths(group): paths = OrderedDict() attributes = OrderedDict() for node in group.childNodes: if getattr(node, 'nodeName', None) == 'g': child_paths, child_attrs = recursively_parse_paths(node) paths.update(child_paths) attributes.update(child_attrs) elif getattr(node, 'nodeName', None) == 'path': node_attrs = attributes_to_dict(node.attributes) if 'transform' in node_attrs.keys(): raise NotImplementedError('Transform found on node. Cannot handle transforms yet') if 'inkscape:label' not in node_attrs.keys(): raise ValueError('All path nodes should have ids on the inkscape:label attribute') path_id = node_attrs['inkscape:label'] attributes[path_id] = node_attrs paths[path_id] = parse_path(node_attrs.get('d')) return paths, attributes
def extract_line_from_path(path, transform=None): """ Treat a path as a line-segment and extract end points. Throws if the path isn't a line. :param path: :param transform: the transform to apply to the coordinates :return: tuple of line-segment start and end coordinates """ path_geom = parse_path(path.attrib["d"]) if len(path_geom) == 1 and is_line(path_geom[0]): line = path_geom[0] # We assume line starts at origin and points towards the second point start_coord = (float_s3(line.start.real), float_s3(line.start.imag)) end_coord = (float_s3(line.end.real), float_s3(line.end.imag)) return apply_transform(start_coord, transform), apply_transform( end_coord, transform) else: raise RuntimeError()
def svg2pathsdom(doc, convert_lines_to_paths, convert_polylines_to_paths, convert_polygons_to_paths, return_svg_attributes): """ Converts an SVG file into a list of Path objects and a list of dictionaries containing their attributes. This currently supports SVG Path, Line, Polyline, and Polygon elements. :param doc: the svg dom document :param convert_lines_to_paths: Set to False to disclude SVG-Line objects (converted to Paths) :param convert_polylines_to_paths: Set to False to disclude SVG-Polyline objects (converted to Paths) :param convert_polygons_to_paths: Set to False to disclude SVG-Polygon objects (converted to Paths) :param return_svg_attributes: Set to True and a dictionary of svg-attributes will be extracted and returned :return: list of Path objects, list of path attribute dictionaries, and (optionally) a dictionary of svg-attributes """ def dom2dict(element): """Converts DOM elements to dictionaries of attributes.""" keys = list(element.attributes.keys()) values = [val.value for val in list(element.attributes.values())] return dict(list(zip(keys, values))) # Use minidom to extract path strings from input SVG paths = [dom2dict(el) for el in doc.getElementsByTagName('path')] d_strings = [el['d'] for el in paths] attribute_dictionary_list = paths # if pathless_svg: # for el in doc.getElementsByTagName('path'): # el.parentNode.removeChild(el) # Use minidom to extract polyline strings from input SVG, convert to # path strings, add to list if convert_polylines_to_paths: plins = [dom2dict(el) for el in doc.getElementsByTagName('polyline')] d_strings += [polyline2pathd(pl['points']) for pl in plins] attribute_dictionary_list += plins # Use minidom to extract polygon strings from input SVG, convert to # path strings, add to list if convert_polygons_to_paths: pgons = [dom2dict(el) for el in doc.getElementsByTagName('polygon')] d_strings += [polyline2pathd(pg['points']) + 'z' for pg in pgons] attribute_dictionary_list += pgons if convert_lines_to_paths: lines = [dom2dict(el) for el in doc.getElementsByTagName('line')] d_strings += [ ('M' + l['x1'] + ' ' + l['y1'] + 'L' + l['x2'] + ' ' + l['y2']) for l in lines ] attribute_dictionary_list += lines # if pathless_svg: # with open(pathless_svg, "wb") as f: # doc.writexml(f) if return_svg_attributes: svg_attributes = dom2dict(doc.getElementsByTagName('svg')[0]) doc.unlink() path_list = [parse_path(d) for d in d_strings] return path_list, attribute_dictionary_list, svg_attributes else: doc.unlink() path_list = [parse_path(d) for d in d_strings] return path_list, attribute_dictionary_list