No código abaixo, esperava que os 3 casos (comentados) fossem totalmente equivalentes. Infelizmente (para mim) o terceiro produz uma linha reta da coordenada (X) à coordenada (Y). Sempre pensei que a \foreach
instrução deveria apenas inserir o código entre colchetes no fluxo de entrada 'como está', mas não parece ser o caso.
o que estou perdendo? ou isso é um bug (conhecido)?
\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: O uso de nós é intencional, para lidar com o tamanho do arco. Além disso, meu caso de uso real está no escopo do pacote tikz cruzamentos. Nesse caso, preciso usar um foreach para lidar com as muitas interseções de caminhos, e então o terceiro caso seria a construção mais natural.
Editar 2: Do argumento nos comentários e da única resposta (por enquanto). Não preciso de formas alternativas de conseguir isso. Já resolvi meu caso da vida real (por meio da implantação de uma estratégia baseada no caso (2)). Minha única pergunta é: O que está acontecendo com o \foreach no terceiro caso? Por que está se comportando assim? Essa é a única questão, não diferentes, formas alternativas de evitar sua armadilha.
Edição 3: Muito obrigado pela resposta de Andrew Stacey que foi além da minha pergunta original. Quer dizer, minha única pergunta era: se o que estava acontecendo era esperado? Agora está claro que é um bug, dada a forma como o foreach é implementado e interage com o subsistema path. Foreach faz mais do que apenas expandir o código entre colchetes substituindo algumas variáveis (as foreach).
Responder1
Como Skillmon diz em seu comentário sobre a resposta, este é um problema de agrupamento. Como está documentado e explorado em várias questões neste site, \foreach
coloca seu código em um grupo (se não me falha a memória, na verdade são dois grupos de profundidade), então qualquer coisa que precise ser lembrada de um loop para o próximo precisa de um pouco de trabalho para tirá-lo desse agrupamento. No nível do usuário, isso geralmente é feito usando a remember
chave, que Qrrbrbirlbel menciona em um de seus comentários sobre sua pergunta.
No entanto, dentro de um caminho, lembrar a coordenada anterior é algo tão útil e comum que o TikZ faz um esforço considerável (literalmente) para garantir que ela seja lembrada corretamente. Portanto, o TikZ instala algum código no início e no final de cada iteração do loop para lembrar o(s) último(s) ponto(s) como se os agrupamentos não estivessem lá. O código que faz isso é relativamente longo:
\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}%
Para referência, na versão atual do TikZ, isso começa na linha 2582 do tikz.code.tex
.
Agora, quando o TikZ encontra um nó sem âncora em um caminho, ele insere uma quebra no caminho adicionando um moveto
ao caminho. O alvo disso moveto
não é conhecido no ponto onde é encontrado, pois escolherá o ponto de saída no limite do nó que é mais adequado para a próxima coordenada. Portanto, ele precisa registrar que moveto
não foi concluído e que, quando o TikZ souber a próxima coordenada, ele a concluirá moveto
antes de descobrir o restante da próxima parte do caminho. Para sinalizar que isso precisa acontecer, o TikZ define uma macro \tikz@moveto@waiting
e esta macro contém o nome do nó que o TikZ precisa para completar o cálculo.
Se você olhar atentamente para o \tikz@foreach
código que postei acima, verá uma clara falta de referência, \tikz@moveto@waiting
o que significa que estenãoseja atualizado no final de cada iteração. Isso significa que cada vez que o TikZ inicia uma iteração, \tikz@moveto@waiting
mantém seu valor original fora do loop. No seu caso, esse é o primeiro nó X
e é por isso que todas as suas linhas começam X
e não no nó anterior.
Felizmente, adicionar as linhas necessárias não é difícil. Aqui está uma versão corrigida 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 testável:
\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}
Atualização: Qrrbrbirlbel observou (em um comentário agora excluído) que ainda havia um problema com o uso to
, e o problema do github estava vinculado aOpção coordenar [turn] no foreacho que levanta o mesmo problema com turn
. Aqui está um código que pretende corrigir tudo isso e possivelmente torna um pouco mais simples adicionar mais coisas que precisam ser salvas.
\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}
Responder2
Isenção de responsabilidade:Esta não é uma resposta real no sentido de que resolve algum problema, apenas ilustra apor quedeste problema com mais precisão do que a questão (sem qualquer análise de código).
Em seu uso, \foreach
o ponto de referência para o próximo segmento de linha não será alterado; portanto, você não está desenhando "apenas a linha para (Y)", mas em vez disso, está desenhando três linhas, (X) -- (nA)
, (nA) -- (nB)
, e (nB) -- (Y)
. A questão é que os pontos para evitar colisões de nós (não tenho ideia de qual é o termo correto em TikZ/ pgf
é e com preguiça de procurar) não são recalculados, então você está desenhando da extremidade inferior esquerda (nA)
para a extremidade inferior esquerda de (nB)
, cruzando seu semicírculo.
Ainda assim, \foreach
o empate pode ter efeitos colaterais estranhos. Se seus pontos inicial e final não forem coordinate
s, mas também node
s, o ponto de referência para o início da próxima linha não será movido e você terminará com três linhas com ponto inicial idêntico.
No geral, eu evitaria completamente o seu terceiro uso, parece bastante imprevisível qual será o resultado. Não tenho ideia se isso é um bug!
\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}
Como você tem uma solução de força bruta para isso, talvez eu ainda possa fornecer uma solução mais bonita (que não use TikZ, mas outros meios). O seguinte fornece resultados corretos sem a necessidade de especificar uma 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}