TikZ: Expandir o traço

Nov 29 2020

Estou tentando desenhar algumas espirais e fiz esta pergunta alguns dias atrás. A resposta de hpekristiansen é ótima e ajuda muito, mas como não está claro se a espiral é direita ou esquerda olhando para a foto, parece um pouco estranho no contexto desejado. Hoje, o próprio hpekristiansen fez uma pergunta sobre esse assunto e obteve uma resposta muito útil de TikZling . Gosto especialmente da resposta usando um \foreachloop para desenhar os segmentos individuais. O problema restante é que não posso usar a doubleopção de caminho, pois seria visível em um fundo não branco ou, como em meu caso de uso, as hastes em torno da espiral.

A solução para este problema seria recortar os caminhos ímpares (começando no terceiro) onde eles são interceptados pelos caminhos pares. Infelizmente, o \path [clip]in Ti k Z usa apenas o centro do caminho para cortar algo e não tem a opção de definir uma largura de linha que seria totalmente cortada. Eu estava, portanto, me perguntando se é possível expandir um caminho de determinada largura de linha para uma forma como é possível com software gráfico vetorial como Adobe Illustrator ou Affinity Designer.

Ao desenhar a espiral em várias seções (parte esquerda do loop e parte direita do loop), isso permitiria usar um código semelhante ao seguinte exemplo:

\documentclass[tikz]{standalone}

\begin{document}
    \begin{tikzpicture}[even odd rule]
        \newcommand{\radiusX}{0.7}
        \newcommand{\radiusY}{1.5}
        \newcommand{\strokeWidth}{0.1}
        \newcommand{\strokeWidthExtra}{0.1}
    
        \newcommand{\background}{({-\radiusX-1},-1) rectangle ({8+\radiusX+1},{2*\radiusY+1})}
        
        \newcommand{\leftArc}{
            (0.5, 0) 
                -- (0, 0) 
                arc (-90:-270:{\radiusX} and {\radiusY}) 
                -- ++(0, -\strokeWidth) 
                arc (90:270:{\radiusX-\strokeWidth} and {\radiusY-\strokeWidth}) 
                -- ++(0.5,0) 
                -- ++(0,-\strokeWidth) 
                -- cycle
        }
        
        \newcommand{\leftArcBig}{
            ({0.5+\strokeWidthExtra}, -\strokeWidthExtra) 
                -- ++({-0.5-\strokeWidthExtra}, 0) 
                arc (-90:-270:{\radiusX+\strokeWidthExtra} and {\radiusY+\strokeWidthExtra}) 
                -- ++(0, {-\strokeWidth-2*\strokeWidthExtra}) 
                arc (90:270:{\radiusX-\strokeWidth-\strokeWidthExtra} and {\radiusY-\strokeWidth-\strokeWidthExtra}) 
                -- ++({0.5+\strokeWidthExtra},0) 
                -- ++(0,{-\strokeWidth+2*\strokeWidthExtra}) 
                -- cycle
        }
        
        \newcommand{\rightArc}{
            (-0.5,0) 
                -- (0,0) 
                arc (-90:90:{\radiusX} and {\radiusY}) 
                -- ++(0,-\strokeWidth) 
                arc (90:-90:{\radiusX-\strokeWidth} and {\radiusY-\strokeWidth}) 
                -- ++(-0.5,0) 
                -- ++(0,-{\strokeWidth}) 
                -- cycle
        }
        
        \newcommand{\rightArcBig}{
            (-{0.5-\strokeWidthExtra},-{\strokeWidthExtra}) 
                -- ++({0.5+\strokeWidthExtra},0) 
                arc (-90:90:{\radiusX+\strokeWidthExtra} and {\radiusY+\strokeWidthExtra}) 
                -- ++(0,{-\strokeWidth-2*\strokeWidthExtra}) 
                arc (90:-90:{\radiusX-\strokeWidth-\strokeWidthExtra} and {\radiusY-\strokeWidth-\strokeWidthExtra}) 
                -- ++({-0.5-\strokeWidthExtra},0) 
                -- ++(0,{-\strokeWidth-2*\strokeWidthExtra}) 
                -- cycle
        }
        
        \shade[clip, top color = gray, bottom color = lightgray] \background;
            
        \begin{scope}
            \fill [black] \rightArc;
            \clip \rightArcBig \background;
                
            \fill [black] \leftArc;
        \end{scope}
        
        \begin{scope}[xshift = 2cm]
            \fill [yellow] \rightArc;
            \fill [yellow, fill opacity = 0.3] \rightArcBig;
            \fill [red] \leftArc;
            \fill [red, fill opacity = 0.3] \leftArcBig;
        \end{scope}
        
        \begin{scope}[xshift = 6cm]
            \fill [black] \leftArc;
            \clip \leftArcBig \background;
                
            \fill [black] \rightArc;
        \end{scope}
        
        \begin{scope}[xshift = 8cm]
            \fill [yellow] \leftArc;
            \fill [yellow, fill opacity = 0.3] \leftArcBig;
            \fill [red] \rightArc;
            \fill [red, fill opacity = 0.3] \rightArcBig;
        \end{scope}
        
    \end{tikzpicture}
\end{document}

Respostas

4 Noname Nov 29 2020 at 10:21

Não é realmente uma resposta. Você está perguntando se existe uma maneira de construir o envelope de um caminho. A resposta é que não existe uma maneira embutida ou simples de fazer isso. Pior ainda, há uma prova analítica de que não existe um caminho simples e geral . Para apreciar a prova, lembre-se de que tudo o que Ti k Z pode fazer é construir curvas de Bézier. Observe que isso não significa que não existe uma maneira não tão simples. Na verdade, o fato de o MetaPost e seus amigos terem rotinas para isso mostra que isso é, em princípio, possível.

Outra ferramenta capaz de fazer isso é o visualizador. OK, vamos deixar o visualizador fazer o trabalho sujo. Isso permite resolver o problema de outra forma, que é conceitualmente igual a este post : fadings. Não é muito conveniente, pelo menos não a implementação seguinte, mas uma prova de princípio. Basicamente, você pode converter um nível de cinza em transparência e, assim, tornar uma linha preta ou branca transparente. Este objeto pode ser colocado em cima de um fundo arbitrário. (Já mencionei que esta implementação não é conveniente?)

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations.pathreplacing,fadings}%
\begin{document}
\begin{tikzfadingfrompicture}[name=custom fade]%
\tikzset{path decomposition/.style={%
    postaction={decoration={show path construction,
    lineto code={
      \draw[#1]  (\tikzinputsegmentfirst) -- (\tikzinputsegmentlast);
    },
    curveto code={
      \draw[#1]  (\tikzinputsegmentfirst) .. controls
        (\tikzinputsegmentsupporta) and (\tikzinputsegmentsupportb)
        ..(\tikzinputsegmentlast) ;
    },
    closepath code={
      \draw[#1]  (\tikzinputsegmentfirst) -- (\tikzinputsegmentlast) {closepath};} }
    ,decorate}},
    cv/.style={black, double=white,line width=0.6mm,double distance=1.2mm}}
\draw[cv,samples=201,domain=-2*pi:2*pi,smooth,
 path decomposition={cv,shorten <=-0.05pt,shorten >=-0.05pt}]
 plot (\x, {cos(10*\x r)} , {sin(10*\x r)} );
\end{tikzfadingfrompicture}%
\begin{tikzpicture}
  \shade[clip, top color = gray!50!black, bottom color = gray!10] 
       (0,-2) rectangle (6,2);
 \path[path fading=custom fade,fit fading=false,
      fill=black] (0,-2) rectangle (8,2);
\end{tikzpicture}
\end{document}

4 AndrewStacey Nov 29 2020 at 22:56

Puramente por coincidência, estou trabalhando em alguns códigos que podem ajudá-lo. Ele é projetado para dividir um caminho em pontos de interseção.

É baseado em minha bibliotecaspath3 ( ctan e github ) que fornece uma estrutura para manipular caminhos depois que eles foram definidos, mas antes de serem corrigidos.

É definitivamente um código experimental e sujeito a alterações, mas seria útil ter um feedback sobre se faz sentido e o que o tornaria útil.

\documentclass{article}

\usepackage{xparse}
\usepackage{tikz}
\usepackage{spath3}
\usetikzlibrary{intersections,hobby,patterns}

\ExplSyntaxOn

\tikzset{
  append~ spath/.code={
    \spath_get_current_path:n {current path}
    \spath_append:nn { current path } { #1 }
    \spath_set_current_path:n { current path }
  },
  set~ spath/.code={
    \spath_set_current_path:n { #1 }
    \spath_get:nnN {#1} {final point} \l__spath_tmpa_tl
    \tl_set:Nx \l__spath_tmpa_tl
    {
      \exp_not:c {tikz@lastx}=\tl_item:Nn \l__spath_tmpa_tl {1}
      \exp_not:c {tikz@lasty}=\tl_item:Nn \l__spath_tmpa_tl {2}
      \exp_not:c {tikz@lastxsaved}=\tl_item:Nn \l__spath_tmpa_tl {1}
      \exp_not:c {tikz@lastysaved}=\tl_item:Nn \l__spath_tmpa_tl {2}
    }
    \tl_use:N \l__spath_tmpa_tl
  },
  shorten~spath~at~end/.code~ 2~ args={
    \spath_shorten:nn {#1} {#2}
  },
  shorten~spath~at~start/.code~ 2~ args ={
    \spath_reverse:n {#1}
    \spath_shorten:nn {#1} {#2}
    \spath_reverse:n {#1}
  },
  shorten~spath~both~ends/.code~ 2~ args={
    \spath_shorten:nn {#1} {#2}
    \spath_reverse:n {#1}
    \spath_shorten:nn {#1} {#2}
    \spath_reverse:n {#1}
  },
  globalise~ spath/.code={
    \spath_globalise:n {#1}
  },
  translate~ spath/.code~ n~ args={3}{
    \spath_translate:nnn {#1}{#2}{#3}
  },
  split~ at~ self~ intersections/.code~ 2~ args={
    \use:c {tikz@addmode}{
      \group_begin:
      \spath_get_current_path:n {spath split tmpa}
      \spath_split_at_self_intersections:nnn {spath split tmpa} {#1} {#2}
      \group_end:
    }
  },
  split~ at~ intersections/.code~ n~ args={5}{
    \spath_split_at_intersections:nnnnn {#1}{#2}{#3}{#4}{#5}
  }
}


\tl_new:N \l__spath_shorten_fa_tl
\tl_new:N \l__spath_shorten_path_tl
\tl_new:N \l__spath_shorten_last_tl
\int_new:N \l__spath_shorten_int
\fp_new:N \l__spath_shorten_x_fp
\fp_new:N \l__spath_shorten_y_fp

\cs_new_nopar:Npn \spath_shorten:nn #1#2
{
  \group_begin:
  \spath_get:nnN {#1} {final action} \l__spath_shorten_fa_tl
  \spath_get:nnN {#1} {path} \l__spath_shorten_path_tl
  \tl_reverse:N \l__spath_shorten_path_tl

  \tl_clear:N \l__spath_shorten_last_tl
  \tl_if_eq:NNTF \l__spath_shorten_fa_tl \g__spath_curveto_tl
  {
    \int_set:Nn \l__spath_shorten_int {3}
  }
  {
    \int_set:Nn \l__spath_shorten_int {1}
  }

  \prg_replicate:nn { \l__spath_shorten_int }
  {
    \tl_put_right:Nx \l__spath_shorten_last_tl
    {
      {\tl_head:N \l__spath_shorten_path_tl}
    }
    \tl_set:Nx \l__spath_shorten_path_tl {\tl_tail:N \l__spath_shorten_path_tl}
    \tl_put_right:Nx \l__spath_shorten_last_tl
    {
      {\tl_head:N \l__spath_shorten_path_tl}
    }
    \tl_set:Nx \l__spath_shorten_path_tl {\tl_tail:N \l__spath_shorten_path_tl}
    \tl_put_right:Nx \l__spath_shorten_last_tl
    {
      \tl_head:N \l__spath_shorten_path_tl
    }
    \tl_set:Nx \l__spath_shorten_path_tl {\tl_tail:N \l__spath_shorten_path_tl}
  }

  \tl_put_right:Nx \l__spath_shorten_last_tl
  {
    {\tl_item:Nn \l__spath_shorten_path_tl {1}}
    {\tl_item:Nn \l__spath_shorten_path_tl {2}}
  }
  \tl_put_right:NV \l__spath_shorten_last_tl \g__spath_moveto_tl
  
  \tl_reverse:N \l__spath_shorten_path_tl

  \fp_set:Nn \l__spath_shorten_x_fp
  {
    \dim_to_fp:n {\tl_item:Nn \l__spath_shorten_last_tl {4}}
    -
    \dim_to_fp:n {\tl_item:Nn \l__spath_shorten_last_tl {1}}
  }
  
  \fp_set:Nn \l__spath_shorten_y_fp
  {
    \dim_to_fp:n {\tl_item:Nn \l__spath_shorten_last_tl {5}}
    -
    \dim_to_fp:n {\tl_item:Nn \l__spath_shorten_last_tl {2}}
  }

  \fp_set:Nn \l__spath_shorten_len_fp
  {
    sqrt( \l__spath_shorten_x_fp * \l__spath_shorten_x_fp +  \l__spath_shorten_y_fp *  \l__spath_shorten_y_fp )
  }

  \fp_set:Nn \l__spath_shorten_len_fp
  {
    (\l__spath_shorten_len_fp - #2)/ \l__spath_shorten_len_fp
  }

  \tl_reverse:N \l__spath_shorten_last_tl
  
  \tl_if_eq:NNTF \l__spath_shorten_fa_tl \g__spath_curveto_tl
  {
    \fp_set:Nn \l__spath_shorten_len_fp
    {
      1 - (1 -\l__spath_shorten_len_fp)/3
    }
    \spath_split_curve:VVNN \l__spath_shorten_len_fp \l__spath_shorten_last_tl
    \l__spath_shorten_lasta_tl
    \l__spath_shorten_lastb_tl
  }
  {
    \spath_split_line:VVNN \l__spath_shorten_len_fp \l__spath_shorten_last_tl
    \l__spath_shorten_lasta_tl
    \l__spath_shorten_lastb_tl
  }

  \prg_replicate:nn {3}
  {
    \tl_set:Nx \l__spath_shorten_lasta_tl {\tl_tail:N \l__spath_shorten_lasta_tl}
  }

  \tl_put_right:NV \l__spath_shorten_path_tl \l__spath_shorten_lasta_tl

  \tl_gset_eq:NN \l__spath_smuggle_tl \l__spath_shorten_path_tl
  \group_end:

  \spath_clear:n {#1}
  \spath_put:nnV {#1} {path} \l__spath_smuggle_tl
}

\cs_generate_variant:Nn \spath_shorten:nn {Vn, VV}
\cs_generate_variant:Nn \spath_reverse:n {V}
\cs_generate_variant:Nn \spath_append_no_move:nn {VV}
\cs_generate_variant:Nn \spath_prepend_no_move:nn {VV}

\cs_new_nopar:Npn \spath_intersect:nn #1#2
{
  \spath_get:nnN {#1} {path} \l__spath_tmpa_tl
  \spath_get:nnN {#2} {path} \l__spath_tmpb_tl
  \pgfintersectionofpaths%
  {%
    \pgfsetpath\l__spath_tmpa_tl
  }{%
    \pgfsetpath\l__spath_tmpb_tl
  }
}

\cs_generate_variant:Nn \spath_intersect:nn {VV, Vn}

\cs_new_nopar:Npn \spath_split_line:nnNN #1#2#3#4
{
  \group_begin:
  \tl_gclear:N \l__spath_smuggle_tl
  \tl_set_eq:NN \l__spath_tmpa_tl \g__spath_moveto_tl
  \tl_put_right:Nx \l__spath_tmpa_tl {
    {\tl_item:nn {#2} {2}}
    {\tl_item:nn {#2} {3}}
  }
  \tl_put_right:NV \l__spath_tmpa_tl \g__spath_lineto_tl
  \tl_put_right:Nx \l__spath_tmpa_tl
  {
    {\fp_to_dim:n
    {
      (1 - #1) * \tl_item:nn {#2} {2} + (#1) * \tl_item:nn {#2} {5}
    }}
    {\fp_to_dim:n
    {
      (1 - #1) * \tl_item:nn {#2} {3} + (#1) * \tl_item:nn {#2} {6}
    }}
  }
  \tl_gset_eq:NN \l__spath_smuggle_tl \l__spath_tmpa_tl
  \group_end:
  \tl_set_eq:NN #3 \l__spath_smuggle_tl
  \group_begin:
  \tl_gclear:N \l__spath_smuggle_tl
  \tl_set_eq:NN \l__spath_tmpa_tl \g__spath_moveto_tl
  \tl_put_right:Nx \l__spath_tmpa_tl
  {
    {\fp_to_dim:n
    {
      (1 - #1) * \tl_item:nn {#2} {2} + (#1) * \tl_item:nn {#2} {5}
    }}
    {\fp_to_dim:n
    {
      (1 - #1) * \tl_item:nn {#2} {3} + (#1) * \tl_item:nn {#2} {6}
    }}
  }
  \tl_put_right:NV \l__spath_tmpa_tl \g__spath_lineto_tl
  \tl_put_right:Nx \l__spath_tmpa_tl {
    {\tl_item:nn {#2} {5}}
    {\tl_item:nn {#2} {6}}
  }
  \tl_gset_eq:NN \l__spath_smuggle_tl \l__spath_tmpa_tl
  \group_end:
  \tl_set_eq:NN #4 \l__spath_smuggle_tl
}

\cs_generate_variant:Nn \spath_split_line:nnNN {nVNN, VVNN}

\int_new:N \l__spath_split_int
\int_new:N \l__spath_splitat_int
\fp_new:N \l__spath_split_fp
\bool_new:N \l__spath_split_bool
\tl_new:N \l__spath_split_path_tl
\tl_new:N \l__spath_split_patha_tl
\tl_new:N \l__spath_split_pathb_tl
\tl_new:N \l__spath_split_intoa_tl
\tl_new:N \l__spath_split_intob_tl
\dim_new:N \l__spath_splitx_dim
\dim_new:N \l__spath_splity_dim

\cs_new_nopar:Npn \spath_split_at:nnnn #1#2#3#4
{
  \group_begin:
  \int_set:Nn \l__spath_splitat_int {\fp_to_int:n {floor(#2) + 1}}
  \fp_set:Nn \l__spath_split_fp {#2 - floor(#2)}
  \int_zero:N \l__spath_split_int
  \bool_set_true:N \l__spath_split_bool

  \spath_get:nnN {#1} {path} \l__spath_split_path_tl
  \tl_clear:N \l__spath_split_patha_tl

  \dim_zero:N \l__spath_splitx_dim
  \dim_zero:N \l__spath_splity_dim

  \bool_until_do:nn {
    \tl_if_empty_p:N \l__spath_split_path_tl
    ||
    \int_compare_p:n { \l__spath_splitat_int == \l__spath_split_int  }
  }
  {
    \tl_set:Nx \l__spath_tmpc_tl {\tl_head:N \l__spath_split_path_tl}
    \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
    \tl_case:Nn \l__spath_tmpc_tl
    {
      \g__spath_lineto_tl
      {
        \int_incr:N \l__spath_split_int
      }
      \g__spath_curvetoa_tl
      {
        \int_incr:N \l__spath_split_int
      }
    }
    \int_compare:nT { \l__spath_split_int < \l__spath_splitat_int  }
    {
      \tl_put_right:NV \l__spath_split_patha_tl \l__spath_tmpc_tl
      
      \tl_put_right:Nx \l__spath_split_patha_tl
      {{ \tl_head:N \l__spath_split_path_tl }}
      \dim_set:Nn \l__spath_splitx_dim {\tl_head:N \l__spath_split_path_tl}
      \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
      
      \tl_put_right:Nx \l__spath_split_patha_tl
      {{ \tl_head:N \l__spath_split_path_tl }}
      \dim_set:Nn \l__spath_splity_dim {\tl_head:N \l__spath_split_path_tl}
      \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
      
    }
  }

  \tl_clear:N \l__spath_split_pathb_tl
  \tl_put_right:NV \l__spath_split_pathb_tl \g__spath_moveto_tl
  \tl_put_right:Nx \l__spath_split_pathb_tl
  {
    {\dim_use:N \l__spath_splitx_dim}
    {\dim_use:N \l__spath_splity_dim}
  }
  \tl_case:Nn \l__spath_tmpc_tl
  {
    \g__spath_lineto_tl
    {
      \tl_put_right:NV \l__spath_split_pathb_tl \l__spath_tmpc_tl
      \tl_put_right:Nx \l__spath_split_pathb_tl
      {{ \tl_head:N \l__spath_split_path_tl }}
      \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
      
      \tl_put_right:Nx \l__spath_split_pathb_tl
      {{ \tl_head:N \l__spath_split_path_tl }}
      \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
      
      \spath_split_line:VVNN \l__spath_split_fp \l__spath_split_pathb_tl
      \l__spath_split_intoa_tl
      \l__spath_split_intob_tl

      \prg_replicate:nn {3} {
        \tl_set:Nx \l__spath_split_intoa_tl {\tl_tail:N \l__spath_split_intoa_tl}
      }

      \tl_put_right:NV \l__spath_split_patha_tl \l__spath_split_intoa_tl
      \tl_put_right:NV \l__spath_split_intob_tl \l__spath_split_path_tl
    }
    \g__spath_curvetoa_tl
    {
      \tl_put_right:NV \l__spath_split_pathb_tl \l__spath_tmpc_tl
      \tl_put_right:Nx \l__spath_split_pathb_tl
      {{ \tl_head:N \l__spath_split_path_tl }}
      \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
      
      \tl_put_right:Nx \l__spath_split_pathb_tl
      {{ \tl_head:N \l__spath_split_path_tl }}
      \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
      
      \prg_replicate:nn {2} {
        
        \tl_put_right:Nx \l__spath_split_pathb_tl
        { \tl_head:N \l__spath_split_path_tl }
        \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
        
        \tl_put_right:Nx \l__spath_split_pathb_tl
        {{ \tl_head:N \l__spath_split_path_tl }}
        \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
      
        \tl_put_right:Nx \l__spath_split_pathb_tl
        {{ \tl_head:N \l__spath_split_path_tl }}
        \tl_set:Nx \l__spath_split_path_tl {\tl_tail:N \l__spath_split_path_tl }
      }

      \spath_split_curve:VVNN \l__spath_split_fp \l__spath_split_pathb_tl
      \l__spath_split_intoa_tl
      \l__spath_split_intob_tl

      \prg_replicate:nn {3} {
        \tl_set:Nx \l__spath_split_intoa_tl {\tl_tail:N \l__spath_split_intoa_tl}
      }

      \tl_put_right:NV \l__spath_split_patha_tl \l__spath_split_intoa_tl
      \tl_put_right:NV \l__spath_split_intob_tl \l__spath_split_path_tl
    }
  }

  \spath_gclear_new:n {#3}
  \spath_gput:nnV {#3} {path} \l__spath_split_patha_tl
  \spath_gclear_new:n {#4}
  \spath_gput:nnV {#4} {path} \l__spath_split_intob_tl
  \group_end:
}

\cs_generate_variant:Nn \spath_split_at:nnnn {VVnn, Vnnn}

\cs_new_nopar:Npn \spath_explode_into_list:nn #1#2
{
  \tl_clear_new:c {l__spath_list_#2}

  \int_zero:N \l__spath_tmpa_int
  \spath_map_segment_inline:nn {#1} {
    \tl_if_eq:NNF ##1 \g__spath_moveto_tl
    {
      \spath_clear_new:n {#2 _ \int_use:N \l__spath_tmpa_int}
      \spath_put:nnV  {#2 _ \int_use:N \l__spath_tmpa_int} {path} ##2
      \tl_put_right:cx {l__spath_list_#2} {{#2 _ \int_use:N \l__spath_tmpa_int}}
      \int_incr:N \l__spath_tmpa_int
    }
  }
}

\tl_new:N \spathselfintersectioncount

\tl_new:N \l__spath_split_tmpa_tl
\tl_new:N \l__spath_split_path_a_tl
\tl_new:N \l__spath_split_path_b_tl
\tl_new:N \l__spath_split_join_a_tl
\tl_new:N \l__spath_split_join_b_tl
\tl_new:N \l__spath_split_first_tl
\tl_new:N \l__spath_split_second_tl

\tl_new:N \l__spath_split_one_tl
\tl_set:Nn \l__spath_split_one_tl {1}
\tl_new:N \l__spath_split_I_tl
\tl_set:Nn \l__spath_split_I_tl {I}

\int_new:N \l__spath_split_count_int
\int_new:N \l__spath_split_intersection_int
\seq_new:N \l__spath_split_segments_seq
\seq_new:N \l__spath_split_segments_processed_seq
\seq_new:N \l__spath_split_segments_middle_seq

\seq_new:N \l__spath_split_joins_seq
\seq_new:N \l__spath_split_joins_processed_seq
\seq_new:N \l__spath_split_joins_middle_seq

\seq_new:N \l__spath_split_intersections_seq

\bool_new:N \l__spath_split_join_bool

% We'll run this on each segment
%
% Arguments:
%  1. Path to split
%  2. Prefix for name of new paths
%  3. List of how to split at intersections
%     A - don't split first path at intersection
%     B - don't split second path at intersection
%     C - split both paths at intersection
%
\cs_new_nopar:Npn \spath_split_at_self_intersections:nnn #1#2#3
{
  \group_begin:
  % The third argument says whether to rejoin segments at the intersections
  \seq_set_split:Nnn \l__spath_split_intersections_seq {} {#3}
  % Clone the path as we'll mess around with it
  \spath_clone:nn {#1} {spath split tmp}
  % Clear the sequence of joining information
  % The join information says whether to rejoin a segment to its predecessor
  \seq_clear:N \l__spath_split_joins_seq
  % Check the last action to see if it is a close path
  \spath_get:nnN {spath split tmp} {final action} \l__spath_split_tmpa_tl
  \tl_if_eq:NNTF \l__spath_split_tmpa_tl \g__spath_closepath_tl
  {
    % Last action is a close, so mark it as needing rejoining
    \seq_put_right:Nn \l__spath_split_joins_seq {1}
  }
  {
    % Last action is not a close, so mark it as needing rejoining
    \seq_put_right:Nn \l__spath_split_joins_seq {0}
  }
  % Remove close paths
  \spath_open_path:n {spath split tmp}
  % Separate into segments (creates a token list)
  \spath_explode_into_list:nn {spath split tmp}{split segments}
  % so convert to a sequence
  \seq_set_split:NnV \l__spath_split_segments_seq {} \l__spath_list_splitsegments

  % Iterate over the number of terms in the sequence, adding the
  % rejoining information
  \int_step_inline:nnnn {1} {1} {\seq_count:N \l__spath_split_segments_seq - 1}
  {
    \seq_put_right:Nn \l__spath_split_joins_seq {1}
  }
  
  % Clear a couple of auxiliaries
  \seq_clear:N \l__spath_split_segments_processed_seq
  \seq_clear:N \l__spath_split_joins_processed_seq
  \int_zero:N \l__spath_split_count_int
  \int_zero:N \l__spath_split_intersection_int

  % Iterate over the sequence
  \bool_while_do:nn
  {
    !\seq_if_empty_p:N \l__spath_split_segments_seq
  }
  {
    % Remove the left-most items for consideration
    \seq_pop_left:NN \l__spath_split_segments_seq \l__spath_split_path_a_tl
    \seq_pop_left:NN \l__spath_split_joins_seq \l__spath_split_join_a_tl

    % Clear some sequences, these will hold any pieces we create from splitting our path under consideration except for the first piece
    \seq_clear:N \l__spath_split_segments_middle_seq
    \seq_clear:N \l__spath_split_joins_middle_seq

    % Put the rejoining information in the processed sequence
    \seq_put_right:NV \l__spath_split_joins_processed_seq \l__spath_split_join_a_tl
    
    % Iterate over the rest of the segments
    \int_step_inline:nnnn {1} {1} {\seq_count:N \l__spath_split_segments_seq}
    {
      % Store the next segment for intersection
      \tl_set:Nx \l__spath_split_path_b_tl {\seq_item:Nn \l__spath_split_segments_seq {##1}}
      % Get the next joining information
      \tl_set:Nx \l__spath_split_join_b_tl {\seq_item:Nn \l__spath_split_joins_seq {##1}}
      % And put it onto our saved stack of joins
      \seq_put_right:NV \l__spath_split_joins_middle_seq \l__spath_split_join_b_tl
      
      % Sort intersections along the first path
      \pgfintersectionsortbyfirstpath
      % Find the intersections of these segments
      \spath_intersect:VV \l__spath_split_path_a_tl \l__spath_split_path_b_tl

      % If we get intersections
      \int_compare:nTF {\pgfintersectionsolutions > 0}
      {
        % Find the times of the first intersection (which will be the first along the segment we're focussing on)
        \pgfintersectiongetsolutiontimes{1}{\l__spath_split_first_tl}{\l__spath_split_second_tl}

        % Ignore intersections that are very near end points
        \bool_if:nT {
          \fp_compare_p:n {
            \l__spath_split_first_tl < .99
          }
          &&
          \fp_compare_p:n {
            \l__spath_split_first_tl > .01
          }
          &&
          \fp_compare_p:n {
            \l__spath_split_second_tl < .99
          }
          &&
          \fp_compare_p:n {
            \l__spath_split_second_tl > .01
          }
        }
        {
          % We have a genuine intersection
          \int_incr:N \l__spath_split_intersection_int
        }

        % Do we split the first path?
        \bool_if:nT {
          \fp_compare_p:n {
            \l__spath_split_first_tl < .99
          }
          &&
          \fp_compare_p:n {
            \l__spath_split_first_tl > .01
          }
        }
        {
          % Split the first path at the intersection
          \spath_split_at:VVnn \l__spath_split_path_a_tl \l__spath_split_first_tl {split \int_use:N \l__spath_split_count_int}{split \int_eval:n { \l__spath_split_count_int + 1}}

          % Put the latter part into our temporary sequence
          \seq_put_left:Nx \l__spath_split_segments_middle_seq {split \int_eval:n{ \l__spath_split_count_int + 1}}
          % Mark this intersection in the joining information
          % Label the breaks as "IA#" and "IB#"
          \seq_put_left:Nx \l__spath_split_joins_middle_seq {IA \int_use:N  \l__spath_split_intersection_int }
          
          % Replace our segment under consideration by the initial part
          \tl_set:Nx \l__spath_split_path_a_tl {split \int_use:N \l__spath_split_count_int }
          % Increment our counter
          \int_incr:N \l__spath_split_count_int
          \int_incr:N \l__spath_split_count_int
        }

        % Do we split the second path?
        \bool_if:nTF {
          \fp_compare_p:n {
            \l__spath_split_second_tl < .99
          }
          &&
          \fp_compare_p:n {
            \l__spath_split_second_tl > .01
          }
        }
        {
          % Split the second segment at the intersection point
          \spath_split_at:VVnn \l__spath_split_path_b_tl \l__spath_split_second_tl {split \int_use:N \l__spath_split_count_int}{split \int_eval:n { \l__spath_split_count_int + 1}}

          % Add these segments to our list of segments we've considered
          \seq_put_right:Nx \l__spath_split_segments_middle_seq {split \int_eval:n{ \l__spath_split_count_int}}
          \seq_put_right:Nx \l__spath_split_segments_middle_seq {split \int_eval:n{ \l__spath_split_count_int + 1}}
          \seq_put_right:Nx \l__spath_split_joins_middle_seq {IB \int_use:N \l__spath_split_intersection_int}
          
          % Increment the counter
          \int_incr:N \l__spath_split_count_int
          \int_incr:N \l__spath_split_count_int
        }
        {
          % If we didn't split the second segment, we just put the second segment on the list of segments we've considered
          \seq_put_right:NV \l__spath_split_segments_middle_seq \l__spath_split_path_b_tl
        }

      }
      {
        % If we didn't split the second segment, we just put the second segment on the list of segments we've considered
        \seq_put_right:NV \l__spath_split_segments_middle_seq \l__spath_split_path_b_tl
      }

    }
    % Having been through the loop for our segment under consideration, we replace the segment list since some of them might have been split and add any remainders of the segment under consideration
    \seq_set_eq:NN \l__spath_split_segments_seq \l__spath_split_segments_middle_seq
    \seq_set_eq:NN \l__spath_split_joins_seq \l__spath_split_joins_middle_seq

    % We add the initial segment to our sequence of dealt with segments
    \seq_put_right:NV \l__spath_split_segments_processed_seq \l__spath_split_path_a_tl
  }

  \seq_clear:N \l__spath_split_segments_seq
  
  \tl_set:Nx \l__spath_split_path_a_tl {\seq_item:Nn \l__spath_split_segments_processed_seq {1}}
  
  \int_step_inline:nnnn {2} {1} {\seq_count:N \l__spath_split_segments_processed_seq}
  {
    % Get the next path and joining information
    \tl_set:Nx \l__spath_split_path_b_tl {\seq_item:Nn \l__spath_split_segments_processed_seq {##1}}
    \tl_set:Nx \l__spath_split_join_b_tl {\seq_item:Nn \l__spath_split_joins_processed_seq {##1}}

    % Do we join this to our previous path?
    \bool_set_false:N \l__spath_split_join_bool

    % If it came from when we split the original path, join them
    \tl_if_eq:NNT \l__spath_split_join_b_tl \l__spath_split_one_tl
    {
      \bool_set_true:N \l__spath_split_join_bool
    }

    % Is this a labelled intersection?
    \tl_set:Nx \l__spath_split_tmpa_tl {\tl_head:N \l__spath_split_join_b_tl}
    \tl_if_eq:NNT \l__spath_split_tmpa_tl \l__spath_split_I_tl
    {
      % Strip off the "I" prefix
      \tl_set:Nx \l__spath_split_tmpa_tl {\tl_tail:N \l__spath_split_join_b_tl}

      % Next letter is "A" or "B"
      \tl_set:Nx \l__spath_split_join_b_tl {\tl_head:N \l__spath_split_tmpa_tl}

      % Remainder is the intersection index
      \int_compare:nTF {\tl_tail:N \l__spath_split_tmpa_tl <= \seq_count:N \l__spath_split_intersections_seq}
      {
        \tl_set:Nx \l__spath_split_join_a_tl {\seq_item:Nn \l__spath_split_intersections_seq {\tl_tail:N \l__spath_split_tmpa_tl}}
      }
      {
        % Default is to rejoin neither segment
        \tl_set:Nn \l__spath_split_join_a_tl {C}
      }

      \tl_if_eq:NNT \l__spath_split_join_a_tl \l__spath_split_join_b_tl
      {
        \bool_set_true:N \l__spath_split_join_bool
      }
      
    }

    \bool_if:NTF \l__spath_split_join_bool
    {
      % Yes, so append it
      \spath_append_no_move:VV \l__spath_split_path_a_tl \l__spath_split_path_b_tl
    }
    {
      % No, so put the first path onto the stack
      \seq_put_right:NV \l__spath_split_segments_seq \l__spath_split_path_a_tl

      % Swap out the paths
      \tl_set_eq:NN \l__spath_split_path_a_tl \l__spath_split_path_b_tl
    }
  }

  % Do we need to add the first path to the last?
  \tl_set:Nx \l__spath_split_join_a_tl {\seq_item:Nn \l__spath_split_joins_processed_seq {1}}

  \tl_if_eq:NNTF \l__spath_split_join_a_tl \l__spath_split_one_tl
  {
    \tl_set:Nx \l__spath_split_path_b_tl {\seq_item:Nn \l__spath_split_segments_processed_seq {1}}
    \spath_prepend_no_move:VV \l__spath_split_path_b_tl \l__spath_split_path_a_tl
    
  }
  {
    \seq_put_right:NV \l__spath_split_segments_seq \l__spath_split_path_a_tl
  }

  % Put our paths into a list
  \int_zero:N \l__spath_split_count_int
  \seq_map_inline:Nn \l__spath_split_segments_seq
  {
    \int_incr:N \l__spath_split_count_int
    \spath_gclone:nn {##1} {#2~\int_use:N \l__spath_split_count_int}
  }
  \tl_gset:NV \spathselfintersectioncount \l__spath_split_count_int
  \group_end:
}

\ExplSyntaxOff

\begin{document}

\begin{tikzpicture}[use Hobby shortcut]

\shade[left color=cyan, right color=magenta, shading angle=90] (-.5,-.2) rectangle (7.5,2.2);
\fill[pattern=bricks, pattern color=white] (-.5,-.2) rectangle (7.5,2.2);

\path
[
split at self intersections={coil}{AAAAAAAAAAAAAAAA}
] ([out angle=0]0,0)
.. +(.85,1) .. +(.25,2) .. +(-.35,1) .. ++(.5,0)
.. +(.85,1) .. +(.25,2) .. +(-.35,1) .. ++(.5,0)
.. +(.85,1) .. +(.25,2) .. +(-.35,1) .. ++([in angle=180].5,0)
;

\foreach \k in {1,..., \spathselfintersectioncount} {
  \tikzset{shorten spath both ends={coil \k}{2pt}, globalise spath=coil \k}
}

\foreach \k in {1,..., 4} {
  \draw[set spath=coil \k];
}

\foreach[evaluate=\l as \xshift using \l*.5cm] \l in {0,...,10} {
  \foreach \k in {5,..., 9} {
    \draw[translate spath={coil \k}{\xshift pt}{0pt},set spath=coil \k];
  }
}

\draw[translate spath={coil 10}{5cm}{0pt},set spath=coil 10];

\end{tikzpicture}
\end{document}

Obviamente, a grande maioria disso acabará por chegar ao spath3pacote e a parte principal está no tikzpicturefinal. O que isso faz é pegar o caminho básico e dividi-lo onde ele se cruza. Em seguida, ele reduz essas peças para criar as lacunas. Essas peças podem então ser reutilizadas (com tradução) para criar a bobina. O resultado é a imagem a seguir, com o fundo para mostrar que não há doubletruques acontecendo aqui.