В коде ниже я ожидал, что 3 случая (закомментированные) будут полностью эквивалентны. К сожалению (для меня) 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 для работы со многими пересечениями путей, и тогда 3-й случай будет наиболее естественной конструкцией.
Редактирование 2: Из аргумента в комментариях и (пока) единственного ответа. Мне не нужны альтернативные способы достижения этого. Я уже решил свой реальный случай (путем развертывания стратегии, основанной на случае (2)). Мой единственный вопрос: что происходит с \foreach в 3-м случае? Почему он так себя ведет? Это единственный вопрос, а не другие, альтернативные способы избежать его ловушки.
Редактирование 3: Большое спасибо за ответ Эндрю Стейси, который превзошел мой первоначальный вопрос. Я имею в виду, что мой единственный вопрос был: если то, что происходило, ожидалось? Теперь ясно, что это ошибка, учитывая способ реализации foreach и взаимодействия с подсистемой путей. Foreach делает больше, чем просто расширяет код в фигурных скобках, заменяя некоторые переменные (foreach).
решение1
Как говорит Skillmon в своем комментарии к своему ответу, это проблема группировки. Как задокументировано и исследовано в различных вопросах на этом сайте, \foreach
помещает свой код в группу (если мне не изменяет память, то на самом деле это две группы глубиной), поэтому все, что нужно запомнить от одного цикла до следующего, требует некоторой работы, чтобы вытащить его из этой группировки. На уровне пользователя это обычно достигается с помощью ключа remember
, который Qrrbrbirlbel упоминает в одном из своих комментариев к вашему вопросу.
Однако внутри пути запоминание предыдущей координаты является настолько полезным и обычным делом, что 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 это начинается на строке 2582 файла tikz.code.tex
.
Теперь, когда TikZ встречает узел без якоря на пути, он вставляет разрыв в путь, добавляя moveto
к пути. Однако цель этого moveto
неизвестна в точке, где он встречается, поскольку он выберет точку выхода на границе узла, которая наиболее подходит для следующей координаты. Поэтому ему нужно записать, что moveto
не было завершено, и что когда TikZ узнает следующую координату, он завершит ее 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}
Обновление: Qrrbrbirlbel отметил (в теперь уже удаленном комментарии), что проблема с использованием все еще существует to
, а проблема с github связана сКоординировать [turn] опцию в 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
внутри розыгрыша могут быть странные побочные эффекты. Если ваши начальная и конечная точки не coordinate
s, а также node
s, точка отсчета для начала следующей линии вообще не перемещается, и вы получаете три линии с одинаковой начальной точкой.
В целом я бы вообще избегал вашего третьего использования, кажется довольно непредсказуемым, какой будет результат. Я понятия не имею, баг ли это!
\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}