tikz \foreach (¿señorita?) comportándose

tikz \foreach (¿señorita?) comportándose

En el código siguiente, esperaba que los 3 casos (comentados) fueran completamente equivalentes. Desafortunadamente (para mí), el tercero produce una línea recta desde la coordenada (X) hasta la coordenada (Y). Siempre pensé que la \foreachdeclaración simplemente insertaba el código entre llaves en el flujo de entrada "tal cual", pero no parece ser el caso.

¿Qué me estoy perdiendo? ¿O se trata de un error (conocido)?

\documentclass{article}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}
  \path 
    (0,0) coordinate(X) 
    (1,1) coordinate(A) node[minimum size=4.3pt,inner sep=0pt](nA){}
    (2,2) coordinate(B) node[minimum size=4.3pt,inner sep=0pt](nB){}
    (3,3) coordinate(Y)
    ;
  \draw[rotate=45] 
    (A) +(-7pt/2,0) arc[start angle=180,end angle=0,radius=7pt/2]
    (B) +(-7pt/2,0) arc[start angle=180,end angle=0,radius=7pt/2]
    ;
    
    % Case (1)
  %\draw (X) -- (nA) -- (nB) -- (Y) ;
  
    % Case (2)
  %\foreach \x/\y in {X/nA,nA/nB,nB/Y} {\draw (\x) -- (\y);}

    % Case (3)
  %\draw (X) \foreach \x in {nA,nB,Y} { -- (\x)};
  
\end{tikzpicture}

\end{document}

Editar: el uso de nodos es intencional para abordar el tamaño del arco. Además, mi caso de uso real está en el alcance del paquete de intersecciones tikz. En cuyo caso necesito usar un foreach para lidiar con las muchas intersecciones de caminos, y luego el tercer caso sería la construcción más natural.

Edición 2: del argumento en los comentarios y la única respuesta (por ahora). No necesito formas alternativas de lograr esto. Ya resolví mi caso de la vida real (mediante el despliegue de una estrategia basada en el caso (2)). Mi única pregunta es: ¿Qué está pasando con \foreach en el tercer caso? ¿Por qué se comporta así? Esa es la única cuestión, no diferentes formas alternativas de evitar su peligro.

Edición 3: Muchas gracias por la respuesta de Andrew Stacey que fue más allá de mi pregunta original. Quiero decir, mi única pregunta era, ¿si se esperaba que sucediera lo que estaba sucediendo? Ahora está claro que es un error, dada la forma en que se implementa foreach e interactúa con el subsistema de ruta. Foreach hace más que simplemente expandir el código entre llaves reemplazando algunas variables (las de foreach).

Respuesta1

Como dice Skillmon en su comentario sobre su respuesta, este es un problema de agrupación. Como está documentado y explorado en varias preguntas en este sitio, \foreachcoloca su código en un grupo (si la memoria no me falla, en realidad tiene dos grupos de profundidad), por lo que cualquier cosa que deba recordarse de un bucle al siguiente necesita un poco de trabajo. para sacarlo de esa agrupación. A nivel de usuario, esto generalmente se logra mediante el uso de la rememberclave, que Qrrbrbirlbel menciona en uno de sus comentarios sobre su pregunta.

Sin embargo, dentro de una ruta, recordar la coordenada anterior es algo tan útil y común que TikZ hace todo lo posible (literalmente) para garantizar que se recuerde correctamente. Entonces TikZ instala algo de código al principio y al final de cada iteración del ciclo para recordar los últimos puntos como si las agrupaciones no estuvieran allí. El código que hace esto es relativamente largo:

\def\tikz@foreach{%
  \def\pgffor@beginhook{%
    \tikz@lastx=\tikz@foreach@save@lastx%
    \tikz@lasty=\tikz@foreach@save@lasty%
    \tikz@lastxsaved=\tikz@foreach@save@lastxsaved%
    \tikz@lastysaved=\tikz@foreach@save@lastysaved%
    \setbox\tikz@figbox=\box\tikz@tempbox%
    \setbox\tikz@figbox@bg=\box\tikz@tempbox@bg%
    \expandafter\tikz@scan@next@command\pgfutil@firstofone}%
  \def\pgffor@endhook{\pgfextra{%
      \xdef\tikz@foreach@save@lastx{\the\tikz@lastx}%
      \xdef\tikz@foreach@save@lasty{\the\tikz@lasty}%
      \xdef\tikz@foreach@save@lastxsaved{\the\tikz@lastxsaved}%
      \xdef\tikz@foreach@save@lastysaved{\the\tikz@lastysaved}%
      \global\setbox\tikz@tempbox=\box\tikz@figbox%
      \global\setbox\tikz@tempbox@bg=\box\tikz@figbox@bg%
      \pgfutil@gobble}}%
  \def\pgffor@afterhook{%
    \tikz@lastx=\tikz@foreach@save@lastx%
    \tikz@lasty=\tikz@foreach@save@lasty%
    \tikz@lastxsaved=\tikz@foreach@save@lastxsaved%
    \tikz@lastysaved=\tikz@foreach@save@lastysaved%
    \let\pgffor@beginhook\relax%
    \let\pgffor@endhook\relax%
    \let\pgffor@afterhook\relax%
    \setbox\tikz@figbox=\box\tikz@tempbox%
    \setbox\tikz@figbox@bg=\box\tikz@tempbox@bg%
    \tikz@scan@next@command}%
  \global\setbox\tikz@tempbox=\box\tikz@figbox%
  \global\setbox\tikz@tempbox@bg=\box\tikz@figbox@bg%
  \xdef\tikz@foreach@save@lastx{\the\tikz@lastx}%
  \xdef\tikz@foreach@save@lasty{\the\tikz@lasty}%
  \xdef\tikz@foreach@save@lastxsaved{\the\tikz@lastxsaved}%
  \xdef\tikz@foreach@save@lastysaved{\the\tikz@lastysaved}%
  \foreach}%

Como referencia, en la versión actual de TikZ, esto comienza en la línea 2582 de tikz.code.tex.

Ahora, cuando TikZ encuentra un nodo sin un ancla en una ruta, inserta una interrupción en la ruta agregando un movetoa la ruta. Sin embargo, el objetivo de esto movetono se conoce en el punto donde se encuentra, ya que elegirá el punto de salida en el límite del nodo que sea más adecuado para la siguiente coordenada. Por lo tanto, debe registrar que movetono se ha completado y que cuando TikZ conozca la siguiente coordenada, la completará movetoantes de descubrir el resto de la siguiente parte del camino. Para indicar que esto debe suceder, TikZ define una macro \tikz@moveto@waitingy esta macro contiene el nombre del nodo que TikZ necesita para completar el cálculo.

Si observa detenidamente el \tikz@foreachcódigo que publiqué anteriormente, verá una clara falta de referencia a\tikz@moveto@waiting lo que significa que estonoactualizarse al final de cada iteración. Eso significa que cada vez que TikZ inicia una iteración, \tikz@moveto@waitingmantiene su valor original desde fuera del ciclo. En su caso, ese es el primer nodo Xy es por eso que todas sus líneas comienzan desde Xel nodo anterior y no desde él.

Afortunadamente, añadir las líneas necesarias no es difícil. Aquí hay una versión parcheada de \tikz@foreach:

\def\tikz@foreach{%
  \def\pgffor@beginhook{%
    \global\let\tikz@moveto@waiting\tikz@foreach@moveto@waiting%
    \tikz@lastx=\tikz@foreach@save@lastx%
    \tikz@lasty=\tikz@foreach@save@lasty%
    \tikz@lastxsaved=\tikz@foreach@save@lastxsaved%
    \tikz@lastysaved=\tikz@foreach@save@lastysaved%
    \setbox\tikz@figbox=\box\tikz@tempbox%
    \setbox\tikz@figbox@bg=\box\tikz@tempbox@bg%
    \expandafter\tikz@scan@next@command\pgfutil@firstofone}%
  \def\pgffor@endhook{\pgfextra{%
      \global\let\tikz@foreach@moveto@waiting\tikz@moveto@waiting%
      \xdef\tikz@foreach@save@lastx{\the\tikz@lastx}%
      \xdef\tikz@foreach@save@lasty{\the\tikz@lasty}%
      \xdef\tikz@foreach@save@lastxsaved{\the\tikz@lastxsaved}%
      \xdef\tikz@foreach@save@lastysaved{\the\tikz@lastysaved}%
      \global\setbox\tikz@tempbox=\box\tikz@figbox%
      \global\setbox\tikz@tempbox@bg=\box\tikz@figbox@bg%
      \pgfutil@gobble}}%
  \def\pgffor@afterhook{%
    \let\tikz@moveto@waiting\tikz@foreach@moveto@waiting%
    \tikz@lastx=\tikz@foreach@save@lastx%
    \tikz@lasty=\tikz@foreach@save@lasty%
    \tikz@lastxsaved=\tikz@foreach@save@lastxsaved%
    \tikz@lastysaved=\tikz@foreach@save@lastysaved%
    \let\pgffor@beginhook\relax%
    \let\pgffor@endhook\relax%
    \let\pgffor@afterhook\relax%
    \setbox\tikz@figbox=\box\tikz@tempbox%
    \setbox\tikz@figbox@bg=\box\tikz@tempbox@bg%
    \tikz@scan@next@command}%
  \global\setbox\tikz@tempbox=\box\tikz@figbox%
  \global\setbox\tikz@tempbox@bg=\box\tikz@figbox@bg%
  \xdef\tikz@foreach@save@lastx{\the\tikz@lastx}%
  \xdef\tikz@foreach@save@lasty{\the\tikz@lasty}%
  \xdef\tikz@foreach@save@lastxsaved{\the\tikz@lastxsaved}%
  \xdef\tikz@foreach@save@lastysaved{\the\tikz@lastysaved}%
  \global\let\tikz@foreach@moveto@waiting\tikz@moveto@waiting%
  \foreach}%

Código comprobable:

\documentclass{article}
%\url{https://tex.stackexchange.com/q/706591/86}
\usepackage{tikz}

\makeatletter
\def\tikz@patched@foreach{%
  \def\pgffor@beginhook{%
    \global\let\tikz@moveto@waiting\tikz@foreach@moveto@waiting%
    \tikz@lastx=\tikz@foreach@save@lastx%
    \tikz@lasty=\tikz@foreach@save@lasty%
    \tikz@lastxsaved=\tikz@foreach@save@lastxsaved%
    \tikz@lastysaved=\tikz@foreach@save@lastysaved%
    \setbox\tikz@figbox=\box\tikz@tempbox%
    \setbox\tikz@figbox@bg=\box\tikz@tempbox@bg%
    \expandafter\tikz@scan@next@command\pgfutil@firstofone}%
  \def\pgffor@endhook{\pgfextra{%
      \global\let\tikz@foreach@moveto@waiting\tikz@moveto@waiting%
      \xdef\tikz@foreach@save@lastx{\the\tikz@lastx}%
      \xdef\tikz@foreach@save@lasty{\the\tikz@lasty}%
      \xdef\tikz@foreach@save@lastxsaved{\the\tikz@lastxsaved}%
      \xdef\tikz@foreach@save@lastysaved{\the\tikz@lastysaved}%
      \global\setbox\tikz@tempbox=\box\tikz@figbox%
      \global\setbox\tikz@tempbox@bg=\box\tikz@figbox@bg%
      \pgfutil@gobble}}%
  \def\pgffor@afterhook{%
    \let\tikz@moveto@waiting\tikz@foreach@moveto@waiting%
    \tikz@lastx=\tikz@foreach@save@lastx%
    \tikz@lasty=\tikz@foreach@save@lasty%
    \tikz@lastxsaved=\tikz@foreach@save@lastxsaved%
    \tikz@lastysaved=\tikz@foreach@save@lastysaved%
    \let\pgffor@beginhook\relax%
    \let\pgffor@endhook\relax%
    \let\pgffor@afterhook\relax%
    \setbox\tikz@figbox=\box\tikz@tempbox%
    \setbox\tikz@figbox@bg=\box\tikz@tempbox@bg%
    \tikz@scan@next@command}%
  \global\setbox\tikz@tempbox=\box\tikz@figbox%
  \global\setbox\tikz@tempbox@bg=\box\tikz@figbox@bg%
  \xdef\tikz@foreach@save@lastx{\the\tikz@lastx}%
  \xdef\tikz@foreach@save@lasty{\the\tikz@lasty}%
  \xdef\tikz@foreach@save@lastxsaved{\the\tikz@lastxsaved}%
  \xdef\tikz@foreach@save@lastysaved{\the\tikz@lastysaved}%
  \global\let\tikz@foreach@moveto@waiting\tikz@moveto@waiting%
  \foreach}%

\DeclareDocumentCommand \FixForeach {}
{
  \let\tikz@foreach=\tikz@patched@foreach
}
\makeatother

\begin{document}

\begin{tikzpicture}
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) -- (B) -- (C);
\end{tikzpicture}

\begin{tikzpicture}
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) \foreach \nd in {B,C} { -- (\nd) };
\end{tikzpicture}

\FixForeach

\begin{tikzpicture}
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) \foreach \nd in {B,C} { -- (\nd) };
\end{tikzpicture}


\end{document}

El resultado del código anterior muestra tres versiones de un diagrama que conecta tres nodos. En el primero, los tres nodos se conectan manualmente en el orden correcto. En el segundo, el código foreach original conecta el segundo y el tercero con el primero. En el tercero, el código foreach parcheado los conecta correctamente nuevamente.


Actualización: Qrrbrbirlbel señaló (en un comentario ahora eliminado) que todavía había un problema con el usoto y que el problema de github estaba vinculado aOpción de coordinar [giro] en foreachlo que plantea el mismo problema con turn. Aquí hay un código que pretende solucionar todo esto y posiblemente haga que sea un poco más sencillo agregar más cosas que deben guardarse.

\documentclass{article}
%\url{https://tex.stackexchange.com/q/706591/86}
\usepackage{tikz}

\makeatletter

\def\pgffor@smugglers@cove{}
\let\tikztostart=\relax

\def\pgffor@smuggle@macro#1{%
  \pgfutil@ifx#1\relax{}{%
    \expandafter
    \pgfutil@g@addto@macro
    \expandafter
    \pgffor@smugglers@cove
    \expandafter
    {%
      \expandafter\def\expandafter#1\expandafter{#1}%
    }%
  }%
}

\def\pgffor@smuggle@dimen#1{%
  \pgfutil@ifx#1\relax{}{%
    \expandafter
    \pgfutil@g@addto@macro
    \expandafter
    \pgffor@smugglers@cove
    \expandafter
    {%
      \expandafter#1\expandafter=\the#1\relax%
    }%
  }%
}

\def\pgffor@smugglers@loot{%
  %
  \pgffor@smuggle@macro\tikz@moveto@waiting%
  \pgffor@smuggle@macro\tikztostart%
  \pgffor@smuggle@macro\tikz@tangent%
  %
  \pgffor@smuggle@dimen\tikz@lastx%
  \pgffor@smuggle@dimen\tikz@lasty%
  \pgffor@smuggle@dimen\tikz@lastxsaved%
  \pgffor@smuggle@dimen\tikz@lastysaved%
  %
}


\def\tikz@patched@foreach{%
  \def\pgffor@beginhook{%
    \pgffor@smugglers@cove
    \gdef\pgffor@smugglers@cove{}%
    \setbox\tikz@figbox=\box\tikz@tempbox%
    \setbox\tikz@figbox@bg=\box\tikz@tempbox@bg%
    \expandafter\tikz@scan@next@command\pgfutil@firstofone}%
  \def\pgffor@endhook{\pgfextra{%
      \pgffor@smugglers@loot%
      \global\setbox\tikz@tempbox=\box\tikz@figbox%
      \global\setbox\tikz@tempbox@bg=\box\tikz@figbox@bg%
      \pgfutil@gobble}}%
  \def\pgffor@afterhook{%
    \pgffor@smugglers@cove
    \gdef\pgffor@smugglers@cove{}%
    \let\pgffor@beginhook\relax%
    \let\pgffor@endhook\relax%
    \let\pgffor@afterhook\relax%
    \setbox\tikz@figbox=\box\tikz@tempbox%
    \setbox\tikz@figbox@bg=\box\tikz@tempbox@bg%
    \tikz@scan@next@command}%
  \global\setbox\tikz@tempbox=\box\tikz@figbox%
  \global\setbox\tikz@tempbox@bg=\box\tikz@figbox@bg%
  \pgffor@smugglers@loot%
  \foreach}%

\DeclareDocumentCommand \FixForeach {}
{
  \let\tikz@foreach=\tikz@patched@foreach
}
\makeatother

\begin{document}

\begin{tabular}{rl}
Paths: &
\begin{tikzpicture}
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) -- (B) -- (C);
\end{tikzpicture} \\
%
Foreach Paths: &
\begin{tikzpicture}
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) \foreach \nd in {B,C} { -- (\nd) };
\end{tikzpicture} \\
%
Tos: &
\begin{tikzpicture}
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) to (B) to (C);
\end{tikzpicture} \\
%
Foreach Tos: &
\begin{tikzpicture}
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) \foreach \nd in {B,C} { to (\nd) };
\end{tikzpicture} \\
%
Turns: &
\begin{tikzpicture}
  \draw[red] (0,0) -- (1,.2) -- ([turn]90:1cm) -- ([turn]90:1cm) -- ([turn]90:1cm) -- ([turn]90:1cm);
\end{tikzpicture} \\
%
Foreach Turns: &
\begin{tikzpicture}
  \draw (0,0) -- (1,.2) foreach \i in {1,2,3,4} { -- ([turn]90:1cm) };
\end{tikzpicture} \\
%
Fixed Foreach Paths: &
\begin{tikzpicture}
\FixForeach
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) \foreach \nd in {B,C} { -- (\nd) };
\end{tikzpicture} \\
%
Fixed Foreach Tos: &
\begin{tikzpicture}
\FixForeach
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) \foreach \nd in {B,C} { to (\nd) };
\end{tikzpicture} \\
%
Fixed Foreach Turns: &
\begin{tikzpicture}
\FixForeach
  \draw (0,0) -- (1,.2) foreach \i in {1,2,3,4} { -- ([turn]90:1cm) };
\end{tikzpicture}


\end{tabular}

\end{document}

Respuesta2

Descargo de responsabilidad:Esta no es una respuesta real en el sentido de que resuelva algún problema, sólo ilustra lapor quéde este problema con mayor precisión que la pregunta (sin ningún análisis de código).

En su uso, \foreachno cambiará el punto de referencia para el siguiente segmento de línea, por lo que no dibujará "solo la línea a (Y)", sino que dibujará tres líneas, (X) -- (nA), (nA) -- (nB)y(nB) -- (Y) . El problema es que los puntos para evitar colisiones de nodos (no tengo idea de cuál es el término correcto en TikZ/ pgfes y es demasiado vago para buscarlo) no se recalculan, por lo que estás dibujando desde el extremo inferior izquierdo de (nA)hasta el extremo inferior izquierdo de (nB), cruzando tu semicírculo.

Aún así, el \foreachinterior del sorteo puede tener efectos secundarios extraños. Si sus puntos inicial y final no son coordinates sino también nodes, el punto de referencia para el comienzo de la siguiente línea no se mueve en absoluto y terminará con tres líneas con un punto inicial idéntico.

En general, evitaría por completo el tercer uso; parece bastante impredecible cuál será el resultado. ¡Sin embargo, no tengo idea de si esto es un error!

\documentclass{article}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}[mynode/.style={minimum size=4.3pt, inner sep=0pt, draw=red}]
  \path
    (0,0) node[mynode](X){}
    (1,0) node[mynode](nA){}
    (1,1) node[mynode](nB){}
    (1,2) node[mynode](Y){}
    ;
    % Case (3)
  \draw (X) \foreach \x in {nA,nB,Y} { -- (\x)};

  \path
    (2,0) coordinate(X)
    (3,0) node[mynode](nA){}
    (3,1) node[mynode](nB){}
    (3,2) coordinate(Y)
    ;
    % Case (3)
  \draw (X) \foreach \x in {nA,nB,Y} { -- (\x)};

  \path
    (4,0) coordinate(X)
    (5,0) coordinate(nA)
    (5,1) coordinate(nB)
    (5,2) coordinate(Y)
    ;
  \draw (X) \foreach \x in {nA,nB,Y} { -- (\x)};
\end{tikzpicture}

\end{document}

ingrese la descripción de la imagen aquí


Dado que tiene una solución de fuerza bruta para esto, tal vez aún pueda proporcionar una solución más bonita (que no use TikZ, pero otros medios). Lo siguiente proporciona resultados correctos sin tener que especificar una lista de pares de coordenadas.

\documentclass[border=3.14,tikz]{standalone}

\ExplSyntaxOn
\cs_new_protected:Npn \tocoordinatelist { \exp:w \__tocoordinatelist:w }
\NewExpandableDocumentCommand \__tocoordinatelist:w { O{--} m }
  { \exp_end: #1 ( \clist_use:nn {#2} { ) #1 ( }) }
\ExplSyntaxOff

\begin{document}

\begin{tikzpicture}[mynode/.style={minimum size=4.3pt, inner sep=0pt, draw=red}]
  \path
    (0,0) coordinate(X)
    (1,0) node[mynode](nA){}
    (1,1) node[mynode](nB){}
    (1,2) coordinate(Y)
    ;
  \draw (X) \tocoordinatelist{nA,nB,Y};
\end{tikzpicture}

\end{document}

ingrese la descripción de la imagen aquí

información relacionada