# Authors: see git history # # Copyright (c) 2010 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. from typing import List, Optional, Iterable from inkex import BaseElement from lxml.etree import Comment from ..commands import is_command, layer_commands from ..debug.utils import safe_get from ..marker import has_marker from ..svg.tags import (CONNECTOR_TYPE, EMBROIDERABLE_TAGS, INKSCAPE_GROUPMODE, NOT_EMBROIDERABLE_TAGS, SVG_CLIPPATH_TAG, SVG_DEFS_TAG, SVG_GROUP_TAG, SVG_IMAGE_TAG, SVG_MASK_TAG, SVG_TEXT_TAG) from ..utils.paths import get_ini from .clone import Clone, is_clone from .element import EmbroideryElement from .empty_d_object import EmptyDObject from .fill_stitch import FillStitch from .image import ImageObject from .marker import MarkerObject from .satin_column import SatinColumn from .stroke import Stroke from .text import TextObject def node_to_elements(node, clone_to_element=False) -> List[EmbroideryElement]: # noqa: C901 if node.style('display') == 'none': return [] if is_clone(node) and not clone_to_element: # clone_to_element: get an actual embroiderable element once a clone has been defined as a clone return [Clone(node)] elif node.tag in EMBROIDERABLE_TAGS and not node.get_path(): return [EmptyDObject(node)] elif has_marker(node): return [MarkerObject(node)] elif node.tag in EMBROIDERABLE_TAGS or is_clone(node): elements: List[EmbroideryElement] = [] from ..sew_stack import SewStack sew_stack = SewStack(node) if not sew_stack.sew_stack_only: element = EmbroideryElement(node) if element.get_style("fill", "black") and not element.get_style('fill-opacity', 1) == "0": elements.append(FillStitch(node)) if element.get_style("stroke"): if element.get_boolean_param("satin_column") and len(element.path) > 1: elements.append(SatinColumn(node)) elif not is_command(element.node): elements.append(Stroke(node)) if element.get_boolean_param("stroke_first", False): elements.reverse() if safe_get(get_ini(), "DEBUG", "sew_stack_enable", default=False): elements.append(sew_stack) return elements elif node.tag == SVG_IMAGE_TAG: return [ImageObject(node)] elif node.tag == SVG_TEXT_TAG: return [TextObject(node)] else: return [] def nodes_to_elements(nodes: Iterable[BaseElement]) -> List[EmbroideryElement]: elements = [] for node in nodes: elements.extend(node_to_elements(node)) return elements def iterate_nodes(node: BaseElement, # noqa: C901 selection: Optional[List[BaseElement]] = None, troubleshoot=False) -> List[BaseElement]: # Postorder traversal of selected nodes and their descendants. # Returns all nodes if there is no selection. def walk(node: BaseElement, selected: bool) -> List[BaseElement]: nodes = [] # lxml-stubs types are wrong, node.tag can be Comment. if node.tag is Comment: # type:ignore[comparison-overlap] return [] element = EmbroideryElement(node) if element.has_command('ignore_object'): return [] if node.tag == SVG_GROUP_TAG and node.get(INKSCAPE_GROUPMODE) == "layer": if len(list(layer_commands(node, "ignore_layer"))): return [] if (node.tag in EMBROIDERABLE_TAGS or node.tag == SVG_GROUP_TAG) and element.get_style('display', 'inline') is None: return [] # defs, masks and clippaths can contain embroiderable elements # but should never be rendered directly. if node.tag in [SVG_DEFS_TAG, SVG_MASK_TAG, SVG_CLIPPATH_TAG]: return [] # command connectors with a fill color set, will glitch into the elements list if is_command(node) or node.get(CONNECTOR_TYPE): return [] if not selected: if selection: if node in selection: selected = True else: # if the user didn't select anything that means we process everything selected = True for child in node: nodes.extend(walk(child, selected)) if selected: if node.tag == SVG_GROUP_TAG: pass elif (node.tag in EMBROIDERABLE_TAGS or is_clone(node)) and not has_marker(node): nodes.append(node) # add images, text and elements with a marker for the troubleshoot extension elif troubleshoot and (node.tag in NOT_EMBROIDERABLE_TAGS or has_marker(node)): nodes.append(node) return nodes return walk(node, False)