def parse_root(root: ElementTree.Element, transform_origin=True, canvas_height=None, draw_hidden=False, visible_root=True, root_transformation=None) -> List[Curve]: """ Recursively parse an etree root's children into geometric curves. :param root: The etree element who's children should be recursively parsed. The root will not be drawn. :param canvas_height: The height of the canvas. By default the height attribute of the root is used. If the root does not contain the height attribute, it must be either manually specified or transform must be False. :param transform_origin: Whether or not to transform input coordinates from the svg coordinate system to standard cartesian system. Depends on canvas_height for calculations. :param draw_hidden: Whether or not to draw hidden elements based on their display, visibility and opacity attributes. :param visible_root: Specifies whether or the root is visible. (Inheritance can be overridden) :param root_transformation: Specifies whether the root's transformation. (Transformations are inheritable) :return: A list of geometric curves describing the svg. Use the Compiler sub-module to compile them to gcode. """ if canvas_height is None: height_str = root.get("height") canvas_height = float(height_str) if height_str.isnumeric() else float( height_str[:-2]) curves = [] # Draw visible elements (Depth-first search) for element in list(root): # display cannot be overridden by inheritance. Just skip the element display = _has_style(element, "display", "none") if display or element.tag == "{%s}defs" % NAMESPACES["svg"]: continue transformation = deepcopy( root_transformation) if root_transformation else None transform = element.get('transform') if transform: transformation = Transformation( ) if transformation is None else transformation transformation.add_transform(transform) # Is the element and it's root not hidden? visible = visible_root and not ( _has_style(element, "visibility", "hidden") or _has_style(element, "visibility", "collapse")) # Override inherited visibility visible = visible or (_has_style(element, "visibility", "visible")) transparent = _has_style(element, "opacity", "0") # If the current element is opaque and visible, draw it if draw_hidden or (visible and not transparent): if element.tag == "{%s}path" % NAMESPACES["svg"]: path = Path(element.attrib['d'], canvas_height, transform_origin, transformation) curves.extend(path.curves) # Continue the recursion curves.extend( parse_root(element, transform_origin, canvas_height, draw_hidden, visible, transformation)) # ToDo implement shapes class return curves
def parse_root(root: ElementTree.Element, transform_origin=True, canvas_height=None, draw_hidden=False, visible_root=True, root_transformation=None) -> List[Curve]: """ Recursively parse an etree root's children into geometric curves. :param root: The etree element who's children should be recursively parsed. The root will not be drawn. :param canvas_height: The height of the canvas. By default the height attribute of the root is used. If the root does not contain the height attribute, it must be either manually specified or transform must be False. :param transform_origin: Whether or not to transform input coordinates from the svg coordinate system to standard cartesian system. Depends on canvas_height for calculations. :param draw_hidden: Whether or not to draw hidden elements based on their display, visibility and opacity attributes. :param visible_root: Specifies whether or the root is visible. (Inheritance can be overridden) :param root_transformation: Specifies whether the root's transformation. (Transformations are inheritable) :return: A list of geometric curves describing the svg. Use the Compiler sub-module to compile them to gcode. """ if canvas_height is None: height_str = root.get("height") viewBox_str = root.get("viewBox") if height_str is None and viewBox_str: # "viewBox" attribute: <min-x, min-y, width, height> height_str = viewBox_str.split()[3] (number, scale_factor) = _parse_length(height_str) canvas_height = number * scale_factor if root.get("viewBox") is None: height_str = root.get("height") if height_str is not None: scale = 25.4/96.0 root_transformation = root_transformation if root_transformation else Transformation() root_transformation.add_scale(scale, scale) else: #print("We have a viewBox of >>%s<<" % (root.get("viewBox"))) # Calulate the transform, as described in https://www.w3.org/TR/SVG/coords.html#ComputingAViewportsTransform p = re.compile(r'([\d\.\-e]+)[,\s]+([\d\.\-e]+)[,\s]+([\d\.\-e]+)[,\s]+([\d\.\-e]+)') if p.search(root.get("viewBox")): parts = p.search(root.get("viewBox")) # TODO Can these values be anything other than numbers? "123mm" maybe? # The spec says they're "number"s, so no units, but possibly + or - or e-notation vb_x = float(parts[1]) vb_y = float(parts[2]) vb_width = float(parts[3]) vb_height = float(parts[4]) # TODO handle the preserveAspectRatio attribute # Defaults if not otherwise specified align = "xMidYMid" meet_or_slice = "meet" e_x = 0.0 e_y = 0.0 width_str = root.get("width") (e_number, e_multiply) = _parse_length(width_str) e_width = e_number * e_multiply e_height = canvas_height scale_x = e_width/vb_width scale_y = e_height/vb_height #print("vb_x: %f, vb_y: %f, vb_width: %f, vb_height: %f" % (vb_x, vb_y, vb_width, vb_height)) #print("e_x: %f, e_y: %f, e_width: %f, e_height: %f, scale_x: %f, scale_y: %f" % (e_x, e_y, e_width, e_height, scale_x, scale_y)) if align != "none" and meet_or_slice == "meet": if scale_x > scale_y: scale_x = scale_y else: scale_y = scale_x if align != "none" and meet_or_slice == "slice": if scale_x < scale_y: scale_x = scale_y else: scale_y = scale_x translate_x = e_x - (vb_x * scale_x) translate_y = e_y - (vb_y * scale_y) # Now apply the viewBox transformations root_transformation = root_transformation if root_transformation else Transformation() if translate_x != 0 or translate_y != 0: root_transformation.add_translation(translate_x, translate_y) if scale_x != 1.0 or scale_y != 1.0: root_transformation.add_scale(scale_x, scale_y) curves = [] # Draw visible elements (Depth-first search) for element in list(root): # display cannot be overridden by inheritance. Just skip the element display = _has_style(element, "display", "none") if display or element.tag == "{%s}defs" % NAMESPACES["svg"]: continue transformation = deepcopy(root_transformation) if root_transformation else None transform = element.get('transform') if transform: transformation = Transformation() if transformation is None else transformation transformation.add_transform(transform) # Is the element and it's root not hidden? visible = visible_root and not (_has_style(element, "visibility", "hidden") or _has_style(element, "visibility", "collapse")) # Override inherited visibility visible = visible or (_has_style(element, "visibility", "visible")) # If the current element is opaque and visible, draw it if draw_hidden or visible: if element.tag == "{%s}path" % NAMESPACES["svg"]: path = Path(element.attrib['d'], canvas_height, transform_origin, transformation) curves.extend(path.curves) # Continue the recursion curves.extend(parse_root(element, transform_origin, canvas_height, draw_hidden, visible, transformation)) # ToDo implement shapes class return curves