2

My TikZ drawing package has a command that will draw a "simple beam", which is basically a rectangle between two arbitrary points. The code below is the relevant part from the package:

% arara: lualatex
\documentclass{article}
\usepackage{expl3}
\usepackage{xparse}
\usepackage{etoolbox}
\usepackage{tikz}
\usetikzlibrary{calc}
% Sets various configuration parameters
% \strandlibset[module]{options as pgfkeys}
\NewDocumentCommand{\strandlibset}{o m}{
  \IfNoValueTF{#1}{
    \pgfqkeys{/strandlib}{#2}
  }
  {
    \pgfqkeys{/strandlib/#1}{#2}
  }
}

\makeatletter
\newif\if@beam@start@round
\newif\if@beam@start@butt
\newif\if@beam@start@rect
\newif\if@beam@end@round
\newif\if@beam@end@butt
\newif\if@beam@end@rect
\pgfkeys{
  /strandlib/beam/.is family,
  /strandlib/beam,
  thickness/.initial=1mm,
  color/.initial=blue,
  @style/.style={/tikz, draw=#1, fill=#1!50!white, thick},
  start/.is choice,
  start/butt/.is if=@beam@start@butt,
  start/rect/.is if=@beam@start@rect,
  start/round/.is if=@beam@start@round,
  start/butt=false,
  start/rect=true,
  start/round=false,
  start=rect,
  end/.is choice,
  end/butt/.is if=@beam@end@butt,
  end/rect/.is if=@beam@end@rect,
  end/round/.is if=@beam@end@round,
  end/butt=false,
  end/rect=true,
  end/round=false,
  end=rect,
}
% \simplebeam[*both rounded]{start}{end}[additional options]
\NewDocumentCommand{\simplebeam}{s m m o}{%
  \begingroup
  \IfValueT{#4}{
    \strandlibset[beam]{#4}
  }
  \IfBooleanT{#1}{
    \strandlibset[beam]{start=round, end=round}
  }
  \if@beam@start@butt
    \coordinate (beamCentreA) at (#2);
  \else
    \coordinate (beamCentreA) at ($(#2)!\pgfkeysvalueof{/strandlib/beam/thickness}!180:(#3)$);
  \fi
  \if@beam@end@butt
    \coordinate (beamCentreB) at (#3);
  \else
    \coordinate (beamCentreB) at ($ (#3)!\pgfkeysvalueof{/strandlib/beam/thickness}!180:(#2)$);
  \fi
  \coordinate (beamCornerA1) at ($ (beamCentreA)!\pgfkeysvalueof{/strandlib/beam/thickness}!90:(beamCentreB)$);
  \coordinate (beamCornerA2) at ($ (beamCentreA)!\pgfkeysvalueof{/strandlib/beam/thickness}!-90:(beamCentreB)$);
  \coordinate (beamCornerB1) at ($ (beamCentreB)!\pgfkeysvalueof{/strandlib/beam/thickness}!-90:(beamCentreA)$);
  \coordinate (beamCornerB2) at ($ (beamCentreB)!\pgfkeysvalueof{/strandlib/beam/thickness}!90:(beamCentreA)$);
  \if@beam@start@round
    \if@beam@end@round
      \filldraw[/strandlib/beam/@style=\pgfkeysvalueof{/strandlib/beam/color}, rounded corners=\pgfkeysvalueof{/strandlib/beam/thickness}] (beamCornerA1) -- (beamCornerA2) -- (beamCornerB2) -- (beamCornerB1) -- cycle;
    \else
      \filldraw[/strandlib/beam/@style=\pgfkeysvalueof{/strandlib/beam/color}, rounded corners=\pgfkeysvalueof{/strandlib/beam/thickness}] (beamCornerA1) -- (beamCornerA2) [sharp corners] -- (beamCornerB2) -- (beamCornerB1) [rounded corners=\pgfkeysvalueof{/strandlib/beam/thickness}] -- cycle;
    \fi
  \else
    \if@beam@end@round
      \filldraw[/strandlib/beam/@style=\pgfkeysvalueof{/strandlib/beam/color}] (beamCornerA1) -- (beamCornerA2) [rounded corners=\pgfkeysvalueof{/strandlib/beam/thickness}] -- (beamCornerB2) -- (beamCornerB1) [sharp corners] -- cycle;
    \else
      \filldraw[/strandlib/beam/@style=\pgfkeysvalueof{/strandlib/beam/color}] (beamCornerA1) -- (beamCornerA2) -- (beamCornerB2) -- (beamCornerB1) -- cycle;
    \fi
  \fi
  \endgroup
}
\makeatother
\begin{document}

Variety of options: some round, some not.
\begin{center}
    \begin{tikzpicture}
        \strandlibset[beam]{color=red}
        \simplebeam{0, 0}{4, 0}
        \simplebeam{0, 2}{5, 2}[thickness=2mm, start=round, end=round]
        \simplebeam{0, 3}{3, 6}[start=butt, end=round]
    \end{tikzpicture}
\end{center}

All round!
\begin{center}
    \begin{tikzpicture}
        \strandlibset[beam]{color=black!50!white, start=round, end=round}
        \simplebeam{0, 0}{4, 0}
        \simplebeam{0, 2}{5, 2}[thickness=2mm, start=rect, end=rect]
        \simplebeam{0, 5}{2, 3}[start=butt, end=round, thickness=1.5mm]
    \end{tikzpicture}
\end{center}
\end{document}

My problem is that if I globally set the beam ends to round (e.g. using \strandlibset[beam]{start=round, end=round}), then I can't change that for a single instance of a beam (e.g. \simplebeam{A}{B}[end=butt]), because the @beam@start@round is true. Essentially what I need is for the start/.is choice and end/.is choice keys to turn off the other booleans: start=butt should imply that @start@round = false and @start@rect = false. What's actually happening is that start=butt simply sets @start@butt = true but doesn't change the other two boolean flags.

How can I fix this?

Some alternate method that achieves the same result would also be accepted, as the nested if-statements are somewhat awkward.

7
  • Why do you have two separate keys for start and end if they should be synchronized? It seems as if you only need one /.is choice key that can take the values butt, rect and round, which determine the line cap on both sides.
    – user194703
    Commented Feb 20, 2020 at 6:36
  • The reason for two keys for the start and end is because sometimes I'll want a beam that has a different cap on each side, e.g. round at the start but rect at the end. The reason I'm posting this question is precisely because of this: I've got a drawing where all the beams except two are round, and those two have a butt cap at one end.
    – Robbie
    Commented Feb 20, 2020 at 6:39
  • 3
    Extract from PDF specifications: "The line cap style shall specify the shape that shall be used at the ends of open subpaths (and dashes, if any) when they are stroked." A stroked path uses only one line cap style! Commented Feb 20, 2020 at 6:50
  • @PaulGaborit Good point! One can still add arrow heads that provide these line caps. This might also the way to go in the answer.
    – user194703
    Commented Feb 20, 2020 at 7:05
  • I figured out a solution: in the \if@beam@start@butt, add a \boolfalse{@beam@start@round} after defining the beam center A coordinate, and likewise for the \if@beam@end@butt. I'm able to sidestep the PDF specifications because while the beams look like lines, they're actually implemented as (rotated) rectangles.
    – Robbie
    Commented Feb 20, 2020 at 7:21

1 Answer 1

1

Here is my proposition using the .code handler to manage three booleans as radio buttons (as exclusive choices):

\documentclass{article}
\usepackage{expl3}
\usepackage{xparse}
\usepackage{etoolbox}
\usepackage{tikz}
\usetikzlibrary{calc}
% Sets various configuration parameters
% \strandlibset[module]{options as pgfkeys}
\NewDocumentCommand{\strandlibset}{o m}{
  \IfNoValueTF{#1}{
    \pgfqkeys{/strandlib}{#2}
  }
  {
    \pgfqkeys{/strandlib/#1}{#2}
  }
}

\makeatletter
\newif\if@beam@start@round
\newif\if@beam@start@butt
\newif\if@beam@start@rect
\newif\if@beam@end@round
\newif\if@beam@end@butt
\newif\if@beam@end@rect
\pgfkeys{
  /strandlib/beam/.is family,
  /strandlib/beam,
  thickness/.initial=1mm,
  color/.initial=blue,
  @style/.style={/tikz, draw=#1, fill=#1!50!white, thick},
  start/.is choice,
  start/butt/.code={
    \@beam@start@butttrue
    \@beam@start@rectfalse
    \@beam@start@roundfalse
  },
  start/rect/.code={
    \@beam@start@buttfalse
    \@beam@start@recttrue
    \@beam@start@roundfalse
  },
  start/round/.code={
    \@beam@start@buttfalse
    \@beam@start@rectfalse
    \@beam@start@roundtrue
  },
  end/.is choice,
  end/butt/.code={
    \@beam@end@butttrue
    \@beam@end@rectfalse
    \@beam@end@roundfalse
  },
  end/rect/.code={
    \@beam@end@buttfalse
    \@beam@end@recttrue
    \@beam@end@roundfalse
  },
  end/round/.code={
    \@beam@end@buttfalse
    \@beam@end@rectfalse
    \@beam@end@roundtrue
  },
  % initial values
  start=rect,
  end=rect,
}
% \simplebeam[*both rounded]{start}{end}[additional options]
\NewDocumentCommand{\simplebeam}{s m m o}{%
  \begingroup
  \IfValueT{#4}{
    \strandlibset[beam]{#4}
  }
  \IfBooleanT{#1}{
    \strandlibset[beam]{start=round, end=round}
  }
  \if@beam@start@butt
    \coordinate (beamCentreA) at (#2);
  \else
    \coordinate (beamCentreA) at ($(#2)!\pgfkeysvalueof{/strandlib/beam/thickness}!180:(#3)$);
  \fi
  \if@beam@end@butt
    \coordinate (beamCentreB) at (#3);
  \else
    \coordinate (beamCentreB) at ($ (#3)!\pgfkeysvalueof{/strandlib/beam/thickness}!180:(#2)$);
  \fi
  \coordinate (beamCornerA1) at ($ (beamCentreA)!\pgfkeysvalueof{/strandlib/beam/thickness}!90:(beamCentreB)$);
  \coordinate (beamCornerA2) at ($ (beamCentreA)!\pgfkeysvalueof{/strandlib/beam/thickness}!-90:(beamCentreB)$);
  \coordinate (beamCornerB1) at ($ (beamCentreB)!\pgfkeysvalueof{/strandlib/beam/thickness}!-90:(beamCentreA)$);
  \coordinate (beamCornerB2) at ($ (beamCentreB)!\pgfkeysvalueof{/strandlib/beam/thickness}!90:(beamCentreA)$);
  \if@beam@start@round
    \if@beam@end@round
      \filldraw[/strandlib/beam/@style=\pgfkeysvalueof{/strandlib/beam/color}, rounded corners=\pgfkeysvalueof{/strandlib/beam/thickness}] (beamCornerA1) -- (beamCornerA2) -- (beamCornerB2) -- (beamCornerB1) -- cycle;
    \else
      \filldraw[/strandlib/beam/@style=\pgfkeysvalueof{/strandlib/beam/color}, rounded corners=\pgfkeysvalueof{/strandlib/beam/thickness}] (beamCornerA1) -- (beamCornerA2) [sharp corners] -- (beamCornerB2) -- (beamCornerB1) [rounded corners=\pgfkeysvalueof{/strandlib/beam/thickness}] -- cycle;
    \fi
  \else
    \if@beam@end@round
      \filldraw[/strandlib/beam/@style=\pgfkeysvalueof{/strandlib/beam/color}] (beamCornerA1) -- (beamCornerA2) [rounded corners=\pgfkeysvalueof{/strandlib/beam/thickness}] -- (beamCornerB2) -- (beamCornerB1) [sharp corners] -- cycle;
    \else
      \filldraw[/strandlib/beam/@style=\pgfkeysvalueof{/strandlib/beam/color}] (beamCornerA1) -- (beamCornerA2) -- (beamCornerB2) -- (beamCornerB1) -- cycle;
    \fi
  \fi
  \endgroup
}
\makeatother
\begin{document}

Variety of options: some round, some not.
\begin{center}
    \begin{tikzpicture}
        \strandlibset[beam]{color=red}
        \simplebeam{0, 0}{4, 0}
        \simplebeam{0, 2}{5, 2}[thickness=2mm, start=round, end=round]
        \simplebeam{0, 3}{3, 6}[start=butt, end=round]
    \end{tikzpicture}
\end{center}

All round!
\begin{center}
    \begin{tikzpicture}
        \strandlibset[beam]{color=black!50!white, start=round, end=round}
        \simplebeam{0, 0}{4, 0}
        \simplebeam{0, 2}{5, 2}[thickness=2mm, start=rect, end=rect]
        \simplebeam{0, 5}{2, 3}[start=butt, end=round, thickness=1.5mm]
    \end{tikzpicture}
\end{center}
\end{document}
1
  • Thanks. It looks neater if I replace the plain TeX with etoolbox LaTeX-type macros, and I'm going through the rest of the code cleaning it up like that. It never ceases to amaze me that other peoples' solutions are obvious answers but ones that just didn't occur to me at the time.
    – Robbie
    Commented Feb 21, 2020 at 6:01

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .