2

I often have to draw cuboids and cubes in TikZ, so I thought I might create a pic for it. For the basics, everything worked fine.

Now, I would want to give it some flexibility, e.g. set fill and draw options for the faces and edges. In order to avoid conflicts, I suppose it would be good to have an own namespace for my new pic.

However, this makes me type quite a lot, so I am wondering whether it was possible to achieve this in another way. This is what I have:

\documentclass[border=5pt]{standalone}

\usepackage{tikz}
\tikzset{
  pics/test/.style args={#1--#2--#3}{
    background code = { 
    \begin{scope}[join=bevel]
    \draw[test/front] (0,0,0) -- ++(#1,0,0) -- ++(0,#2,0) -- ++(-#1,0,0) -- cycle;
    \draw[test/top] (0,#2,0) -- ++(#1,0,0) -- ++(0,0,-#3) -- ++(-#1,0,0) -- cycle;
    \draw[test/right] (#1,0,0) -- ++(0,#2,0) -- ++(0,0,-#3) -- ++(0,-#2,0) -- cycle;
    \end{scope}
    }
  },
  pics/test/.default={1--1--1},
  test/.is family,
  test,
  front/.style={fill=white},
  right/.style={fill=white},
  top/.style={fill=white}
}

\begin{document}
   \begin{tikzpicture}
      \pic[test/front/.style={fill=red!20}] at (0,0,0) {test=10--5--5};
      \pic at (0,5,0) {test};
   \end{tikzpicture}
\end{document}

I would prefer being able to write something like \pic[front={fill=red!20},edges={thick,draw=blue},hidden edges={dashed}] {test=10--5--5}. (I know that I do not have set up all of those parameters yet; I will do so once I am sure about the practice to adopt.)

I have two vague ideas:

  • While reading the source of the angles library, I saw that it is possible to define a setup code that parses the parameters.
  • Maybe I could use the pic action.

What would the experts around here consider a good practice?

  • For global styles, why use a namespace at all? If a style is particular to an image, define it in that image (either in the optional argument of tikzpicture or with a \tikzset inside). Anyway, are "best practice" requests appropriate questions here? I think it's quite subjective. – Raphael Sep 9 '15 at 9:45
  • I would want to develop it in a way that would permit integration as a library, even if that will probably not happen, because my approach might be too basic. As such, I think my stuff should not mess with the things that are already there. – Philipp Imhof Sep 9 '15 at 10:42
  • I'm not an expert on library design, but I don't think a library would use global styles like this. It'd probably define new commands and environments, which can then selectively and locally define styles. – Raphael Sep 9 '15 at 10:45
  • I do not want to use global styles. I want to globally define a pic and keep all its options in a separate namespace (or however one would call this). To me, that's the opposite of a global style which could then interfere with the rest. My pic should be configurable, but not bother anyone who does not need it. – Philipp Imhof Sep 9 '15 at 11:00
  • So define a new command or a savebox for that picture? There's not reason to "leak" the style at all. In programming terms, use encapsulation. – Raphael Sep 9 '15 at 11:45
5

I finally came to a result that I wish to share with the community. Of course, I am always thankful for comments and critics.

\documentclass[border=5pt]{standalone}

\usepackage{tikz}
\usetikzlibrary{3d}

\makeatletter
\def\tikz@lib@cuboid@get#1{\pgfkeysvalueof{/tikz/cuboid/#1}}

\def\tikz@lib@cuboid@setup{%
   \pgfmathsetlengthmacro{\vxx}%
      {\tikz@lib@cuboid@get{xscale}*cos(\tikz@lib@cuboid@get{xangle})*1cm}
   \pgfmathsetlengthmacro{\vxy}%
      {\tikz@lib@cuboid@get{xscale}*sin(\tikz@lib@cuboid@get{xangle})*1cm}
   \pgfmathsetlengthmacro{\vyx}%
      {\tikz@lib@cuboid@get{yscale}*cos(\tikz@lib@cuboid@get{yangle})*1cm}
   \pgfmathsetlengthmacro{\vyy}%
      {\tikz@lib@cuboid@get{yscale}*sin(\tikz@lib@cuboid@get{yangle})*1cm}
   \pgfmathsetlengthmacro{\vzx}%
      {\tikz@lib@cuboid@get{zscale}*cos(\tikz@lib@cuboid@get{zangle})*1cm}
   \pgfmathsetlengthmacro{\vzy}%
      {\tikz@lib@cuboid@get{zscale}*sin(\tikz@lib@cuboid@get{zangle})*1cm}
}

\def\tikz@lib@cuboid@draw#1--#2--#3\pgf@stop{%
    \begin{scope}[join=bevel,x={(\vxx,\vxy)},y={(\vyx,\vyy)},z={(\vzx,\vzy)}]
       % first draw the faces with global and individual style
       % then draw the grids
       \begin{scope}[canvas is yz plane at x=#1]
          \draw[cuboid/all faces,cuboid/right face] (0,0) -- ++(#2,0) 
                -- ++(0,-#3) -- ++(-#2,0) -- cycle;
          \draw[cuboid/all grids,cuboid/right grid] (0,0) grid (#2,-#3);
       \end{scope}
       \begin{scope}[canvas is xy plane at z=0]
          \draw[cuboid/all faces,cuboid/front face] (0,0) -- ++(#1,0) -- 
                ++(0,#2) -- ++(-#1,0) -- cycle;
          \draw[cuboid/all grids,cuboid/front grid] (0,0) grid (#1,#2);
       \end{scope}
       \begin{scope}[canvas is xz plane at y=#2]
          \draw[cuboid/all faces,cuboid/top face] (0,0) -- ++(#1,0) -- 
                ++(0,-#3) -- ++(-#1,0) -- cycle;
          \draw[cuboid/all grids,cuboid/top grid] (0,0) grid (#1,-#3);
       \end{scope}
       % now, draw the hidden edges
       \draw[cuboid/hidden edges] (0,#2,-#3) -- (0,0,-#3) -- (0,0,0) 
                (0,0,-#3) -- ++(#1,0,0);
       % finally, define the anchors: 8 vertices
       \path (0,#2,0) coordinate (-left top front)
                      coordinate (-left front top)
                      coordinate (-top left front)
                      coordinate (-top front left)
                      coordinate (-front top left)
                      coordinate (-front left top);
       \path (0,#2,-#3) coordinate (-left top rear)
                        coordinate (-left rear top)
                        coordinate (-top left rear)
                        coordinate (-top rear left)
                        coordinate (-rear top left)
                        coordinate (-rear left top);
       \path (0,0,-#3) coordinate (-left bottom rear)
                       coordinate (-left rear bottom)
                       coordinate (-bottom left rear)
                       coordinate (-bottom rear left)
                       coordinate (-rear bottom left)
                       coordinate (-rear left bottom);
       \path (0,0,0) coordinate (-left bottom front)
                     coordinate (-left front bottom)
                     coordinate (-bottom left front)
                     coordinate (-bottom front left)
                     coordinate (-front bottom left)
                     coordinate (-front left bottom);
       \path (#1,#2,0) coordinate (-right top front)
                       coordinate (-right front top)
                       coordinate (-top right front)
                       coordinate (-top front right)
                       coordinate (-front top right)
                       coordinate (-front right top);
       \path (#1,#2,-#3) coordinate (-right top rear)
                         coordinate (-right rear top)
                         coordinate (-top right rear)
                         coordinate (-top rear right)
                         coordinate (-rear top right)
                         coordinate (-rear right top);
       \path (#1,0,-#3) coordinate (-right bottom rear)
                        coordinate (-right rear bottom)
                        coordinate (-bottom right rear)
                        coordinate (-bottom rear right)
                        coordinate (-rear bottom right)
                        coordinate (-rear right bottom);
       \path (#1,0,0) coordinate (-right bottom front)
                      coordinate (-right front bottom)
                      coordinate (-bottom right front)
                      coordinate (-bottom front right)
                      coordinate (-front bottom right)
                      coordinate (-front right bottom);
       % centers of the 6 faces
       \coordinate (-left center) at (0,.5*#2,-.5*#3);
       \coordinate (-right center) at (#1,.5*#2,-.5*#3);
       \coordinate (-top center) at (.5*#1,#2,-.5*#3);
       \coordinate (-bottom center) at (.5*#1,0,-.5*#3);
       \coordinate (-front center) at (.5*#1,.5*#2,0);
       \coordinate (-rear center) at (.5*#1,.5*#2,-#3);
       % center of the cuboid
       \coordinate (-center) at (.5*#1,.5*#2,-.5*#3);
       % centers of the 12 edges
       \path (0,#2,-.5*#3) coordinate (-left top center) 
                           coordinate (-top left center);
       \path (.5*#1,#2,-#3) coordinate (-top rear center)
                            coordinate (-rear top center);
       \path (#1,#2,-.5*#3) coordinate (-right top center)
                            coordinate (-top right center);
       \path (.5*#1,#2,0) coordinate (-top front center)
                          coordinate (-front top center);
       \path (0,0,-.5*#3) coordinate (-left bottom center) 
                           coordinate (-bottom left center);
       \path (.5*#1,0,-#3) coordinate (-bottom rear center)
                            coordinate (-rear bottom center);
       \path (#1,0,-.5*#3) coordinate (-right bottom center)
                            coordinate (-bottom right center);
       \path (.5*#1,0,0) coordinate (-bottom front center)
                          coordinate (-front bottom center);
       \path (0,.5*#2,0) coordinate (-left front center) 
                           coordinate (-front left center);
       \path (0,.5*#2,-#3) coordinate (-left rear center)
                            coordinate (-rear left center);
       \path (#1,.5*#2,0) coordinate (-right front center)
                            coordinate (-front right center);
       \path (#1,.5*#2,-#3) coordinate (-right rear center)
                          coordinate (-rear right center);
    \end{scope}
}

\tikzset{
  pics/cuboid/.style = {
    setup code = \tikz@lib@cuboid@setup,
    background code = \tikz@lib@cuboid@draw#1\pgf@stop
  },
  pics/cuboid/.default={1--1--1},
  cuboid/.is family,
  cuboid,
  all faces/.style={fill=white},
  all grids/.style={draw=none},
  front face/.style={},
  front grid/.style={},
  right face/.style={},
  right grid/.style={},
  top face/.style={},
  top grid/.style={},
  edges/.style={},
  hidden edges/.style={draw=none},
  xangle/.initial=0,
  yangle/.initial=90,
  zangle/.initial=210,
  xscale/.initial=1,
  yscale/.initial=1,
  zscale/.initial=0.5
}

\newcommand{\tikzcuboidreset}{
\tikzset{cuboid,
  all faces/.style={fill=white},
  all grids/.style={draw=none},
  front face/.style={},
  front grid/.style={},
  right face/.style={},
  right grid/.style={},
  top face/.style={},
  top grid/.style={},
  edges/.style={},
  hidden edges/.style={draw=none},
  xangle=0,
  yangle=90,
  zangle=210,
  xscale=1,
  yscale=1,
  zscale=0.5
}
}

\newcommand{\tikzcuboidset}{\@ifstar\tikzcuboidset@star\tikzcuboidset@nostar} 
\newcommand{\tikzcuboidset@nostar}[1]{\tikzcuboidreset\tikzset{cuboid,#1}}
\newcommand{\tikzcuboidset@star}[1]{\tikzset{cuboid,#1}}
\makeatother

\begin{document}
   \begin{tikzpicture}
      \pic[ultra thick,red] at (0,0,0) {cuboid=2--2--2};

      \tikzcuboidset{hidden edges/.style={dashed}}
      \pic[thick,blue] (cuboid) at (4,0,0) {cuboid=3--3--3};
      \fill[red] (cuboid-rear left center) circle (2pt);

      \tikzcuboidset*{zangle=225}
      \pic[thick,blue] at (8,0,0) {cuboid};

      \tikzcuboidset{all grids/.style={draw=blue,thin,step=.5}}
      \pic[thin,blue] at (10,0,0) {cuboid};

     \tikzcuboidset{hidden edges/.style={dashed},front face/.style={fill=red!20},right face/.style={fill=blue!20},top face/.style={fill=green!20}}
      \pic at (9,2,0) {cuboid};
   \end{tikzpicture} 
\end{document}

I find the result quite appealing:

enter image description here

A few notes on syntax and usage

  • One uses \tikzcuboidset to configure the cuboid's details. Otherwise, reasonable (to my taste) default values are used.
  • Usage of \tikzcuboidset* allows to add new parameters while keeping the things that were previously set.
  • I used xsize--ysize--zsize syntax in order to avoid the need of curly braces for handing over the sizes, it is not quite typical TikZ usage, but I find it very handy.
  • We have anchors on all vertices, all face centers, the center of the solid and the centers of the edges. They are all reasonably named and aliases are defined. This allows to set nodes in order to label the edges.

What I do not like is the code duplication for the \tikzcuboidreset macro. Would it be possible to say something like "set it all to the default values defined above"?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.