tikz \foreach (miss?) 행동

tikz \foreach (miss?) 행동

아래 코드에서는 3가지 경우(주석 처리)가 완전히 동일할 것으로 예상했습니다. 불행히도 (나에게는) 세 번째 것은 좌표 (X)에서 좌표 (Y)까지 직선을 생성합니다. 나는 항상 \foreach중괄호 사이에 코드를 '있는 그대로' 입력 스트림에 삽입하는 명령문이라고 생각했지만 그렇지 않은 것 같습니다.

내가 무엇을 놓치고 있나요? 아니면 (알려진) 버그인가요?

\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}

편집: 호 크기를 처리하기 위해 노드를 사용하는 것은 의도적인 것입니다. 게다가 내 실제 사용 사례는 tikz 교차로 패키지의 범위에 있습니다. 이 경우 많은 경로 교차를 처리하기 위해 foreach를 사용해야 하며 세 번째 경우가 가장 자연스러운 구성이 됩니다.

편집 2: 의견의 주장과 (현재는) 유일한 답변에서. 이를 달성하기 위한 다른 방법은 필요하지 않습니다. 나는 이미 내 실제 사례를 해결했습니다(사례 (2)를 기반으로 한 전략 배포를 통해). 내 유일한 질문은 다음과 같습니다. 세 번째 경우의 \foreach에서는 무슨 일이 일어나고 있나요? 왜 그렇게 행동하는 걸까요? 이것이 함정을 피하는 대안적인 방법이 아닌 유일한 질문입니다.

편집 3: 원래 질문을 뛰어 넘는 Andrew Stacey의 답변에 감사드립니다. 내 말은, 내 유일한 질문은, 만약 무슨 일이 일어나고 있는지, 예상했던 일이냐는 것이었습니다. 이제 foreach가 구현되고 경로 하위 시스템과 상호 작용하는 방식을 고려하면 이는 버그라는 것이 분명해졌습니다. Foreach는 일부 변수(foreach 변수)를 대체하는 중괄호 안의 코드를 확장하는 것 이상의 작업을 수행합니다.

답변1

Skillmon이 답변에 대한 의견에서 말했듯이 이는 그룹화 문제입니다. 문서화되어 있고 이 사이트의 다양한 질문에서 살펴본 대로 \foreach코드를 그룹에 넣습니다(기억이 제대로 작동한다면 실제로는 두 그룹 깊이입니다). 따라서 한 루프에서 다음 루프까지 기억해야 하는 모든 항목에는 약간의 작업이 필요합니다. 해당 그룹에서 꺼내려면 사용자 수준에서 이는 일반적으로 rememberQrrbrbirlbel이 귀하의 질문에 대한 의견 중 하나에서 언급한 키를 사용하여 수행됩니다.

그러나 경로 내에서 이전 좌표를 기억하는 것은 매우 유용하고 일반적인 것이므로 TikZ는 올바르게 기억되도록 하기 위해 문자 그대로 상당한 길이를 사용합니다. 따라서 TikZ는 루프의 각 반복 시작과 끝 부분에 일부 코드를 설치하여 그룹화가 없는 것처럼 마지막 지점을 기억합니다. 이를 수행하는 코드는 비교적 깁니다.

\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}%

참고로 현재 버전의 TikZ에서는 tikz.code.tex.

이제 TikZ는 경로에 앵커가 없는 노드를 발견하면 moveto경로에 a를 추가하여 경로에 중단을 삽입합니다. 그러나 그 목표는 moveto다음 좌표에 가장 적합한 노드 경계의 출구 지점을 선택하기 때문에 만나는 지점에서는 알 수 없습니다. 따라서 완료되지 않았음을 기록해야 하며 movetoTikZ가 다음 좌표를 알면 moveto경로의 나머지 다음 부분을 파악하기 전에 해당 좌표를 완료할 것임을 기록해야 합니다. 이러한 일이 발생해야 함을 표시하기 위해 TikZ는 매크로를 정의 \tikz@moveto@waiting하고 이 매크로는 TikZ가 계산을 완료하는 데 필요한 노드 이름을 보유합니다.

\tikz@foreach위에 게시한 코드를 자세히 살펴보면 참조가 뚜렷하게 부족하다는 \tikz@moveto@waiting것을 알 수 있습니다.그렇지 않다각 반복이 끝날 때마다 업데이트됩니다. 이는 TikZ가 반복을 시작할 때마다 \tikz@moveto@waiting루프 외부의 원래 값을 유지한다는 것을 의미합니다. 귀하의 경우 이것이 첫 번째 노드 X이므로 모든 라인이 X이전 노드에서 시작되고 이전 노드에서 시작되지 않습니다.

다행히 필요한 줄을 추가하는 것은 어렵지 않습니다. 패치된 버전은 다음과 같습니다 \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}%

테스트 가능한 코드:

\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}

위 코드의 출력은 세 개의 노드를 연결하는 세 가지 버전의 다이어그램을 보여줍니다. 첫 번째에서는 세 개의 노드가 올바른 순서로 수동으로 연결됩니다. 두 번째에서는 원래 foreach 코드가 두 번째와 세 번째를 첫 번째에 연결합니다. 세 번째에서는 패치된 foreach 코드가 이를 다시 올바르게 연결합니다.


업데이트: Qrrbrbirlbel은 (지금은 삭제된 댓글에서) 을 사용하는 데 여전히 문제가 있으며 togithub 문제가 다음에 연결되어 있다고 언급했습니다.foreach의 좌표 [회전] 옵션와 같은 문제가 발생합니다 turn. 다음은 이러한 모든 문제를 해결하고 저장해야 할 항목을 더 추가하는 것을 약간 더 간단하게 만드는 일부 코드입니다.

\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}

답변2

부인 성명:이는 일부 문제를 해결한다는 의미에서 실제 답변이 아니며 단지코드 분석 없이 이 문제를 질문보다 더 정확하게 설명합니다.

사용법에서는 \foreach다음 선분의 참조점을 변경하지 않으므로 "(Y)에 대한 선만 그리는 것이 아니라 대신 , , 및 세 개의 선을 그리는 것 (X) -- (nA)입니다 (nA) -- (nB).(nB) -- (Y) . 문제는 노드의 충돌 회피를 위한 포인트(Ti의 올바른 용어가 무엇인지 모르겠습니다)입니다.케이Z/는 pgf찾아보기에는 너무 게으르다)은 다시 계산되지 않으므로 의 왼쪽 하단 끝에서 의 (nA)왼쪽 하단 끝 (nB)으로 반원을 가로질러 그리는 것입니다.

여전히 \foreach드로우 내부에는 이상한 부작용이 있을 수 있습니다. 시작점과 끝점이 coordinates가 아니고 s인 경우 node다음 줄의 시작에 대한 참조점은 전혀 이동되지 않고 동일한 시작점을 갖는 세 개의 선으로 끝납니다.

전반적으로 나는 세 번째 사용을 완전히 피하고 싶습니다. 결과가 어떻게 될지 예측할 수 없는 것 같습니다. 그래도 이것이 버그인지는 전혀 모르겠습니다!

\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}

여기에 이미지 설명을 입력하세요


이에 대한 무차별적인 솔루션이 있으므로 아마도 Ti를 사용하지 않는 더 예쁜 솔루션을 제공할 수 있을 것입니다.케이Z, 그러나 다른 수단). 다음은 좌표 쌍 목록을 지정하지 않고도 올바른 결과를 제공합니다.

\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}

여기에 이미지 설명을 입력하세요

관련 정보