12

I'm trying to draw a set of rotated rectangular cuboids around a centre point in Tikz. I've created an example of what I'm trying to achieve in OpenSCAD below

set of cuboids in a circle

with the code to generate the above figure in OpenSCAD:

 for (i = [0:45:360]) {
    rotate([0,i,0])
    {
        translate([2,0,-0.5]){
            cube([2,0.5,1]);
        }
    }
}

The purpose is to draw the ferrites in an Inductive Power Transfer Magnetic Coupler (pad), an example of the diagram I would like to draw is below

Circular IPT Pad

I think, the other parts are quite achievable, but the rotated blocks are a challenge. I guess because, as many people have noted, TikZ does not have a 3D engine.

I have had a go at making something like what I want here:

\documentclass[border=7pt]{standalone}

\usepackage{tikz}
\usepackage{tikz-3dplot}

%%%%%%%%%%%

\begin{document}

    \tdplotsetmaincoords{-60}{-50}

    \begin{tikzpicture}
    [
        tdplot_main_coords,
    ]

        \def\rounding{0.2pt};
        \foreach \i in {10,50,...,360}
        {
            \begin{scope}[rotate around={\i:(0.4,0,0.0)}]
                \pgfmathsetmacro{\cubex}{1}
                \pgfmathsetmacro{\cubey}{0.1}
                \pgfmathsetmacro{\cubez}{0.1}
                % http://tex.stackexchange.com/questions/12020/what-is-the-easiest-way-to-draw-3d-cube-with-tikz
                \draw[rounded corners = \rounding,fill=black!70] (0,0,0) -- ++(-\cubex,0,0) -- ++(0,-\cubey,0) -- ++(\cubex,0,0) -- cycle;
                \draw[rounded corners = \rounding,fill=black!70] (0,0,0) -- ++(0,0,-\cubez) -- ++(0,-\cubey,0) -- ++(0,0,\cubez) -- cycle;
                \draw[rounded corners = \rounding,fill=black!70] (0,0,0) -- ++(-\cubex,0,0) -- ++(0,0,-\cubez) -- ++(\cubex,0,0) -- cycle;
            \end{scope}
        }

    \end{tikzpicture}

\end{document}

which currently looks like this

Cubes weirdly arranged in a flat circle

I had to set the \tdplotsetmaincoords{-60}{-50} to a negative number, otherwise you see the 'back' of the cuboid.

I don't know, how to make the circle of cuboids lean back into the page, any help would be greatly appreciated. I also don't know, how to rotate the cubes so they look right, at the moment it looks quite odd.

3
  • Very good. Excellent for my opinion your strategy.
    – Sebastiano
    Jul 13, 2019 at 8:52
  • 1
    offtopic: "Inductive Power Transfer Magnetic Coupler" sounds like something Scotty or LaForge would say in Star Trek... :D
    – naphaneal
    Jul 13, 2019 at 9:06
  • Haha! Thanks for reminding me that it is quite cool! Sometimes its easy for things to become mundane when its around all time time. Now, Wikipedia on a cell phone, that is certainly science fiction become science fact! Jul 15, 2019 at 1:59

1 Answer 1

20

The proper way to rotate things in 3d with tikz-3dplot is to use rotated coordinates, for rotations about the z-axis you can use \tdplotsetrotatedcoords{\i}{00}{0}. Then the cubes which you are using were drawn for specific view angles. In order to make them fully rotatable you need to, depending on the view angles, draw different faces. To this end, I am using a tool that figures out what the projections are: get projections. This allows one to draw (only) the faces that are closest to the observer (and which is why there are the \ifnums that look at the x, y and z projections).

\documentclass[tikz,border=7pt]{standalone}
\usepackage{tikz-3dplot}
\tikzset{get projections/.style={insert path={%
let \p1=(1,0,0),\p2=(0,1,0)  in 
[/utils/exec={\pgfmathtruncatemacro{\xproj}{sign(\x1)}\xdef\xproj{\xproj}
\pgfmathtruncatemacro{\yproj}{sign(\x2)}\xdef\yproj{\yproj}
\pgfmathtruncatemacro{\zproj}{sign(cos(\tdplotmaintheta))}\xdef\zproj{\zproj}}]}},
}
%%%%%%%%%%%

\begin{document}

\tdplotsetmaincoords{-60}{-50}

\begin{tikzpicture}[tdplot_main_coords]
    \pgfmathsetmacro{\cubex}{1}
    \pgfmathsetmacro{\cubey}{0.1}
    \pgfmathsetmacro{\cubez}{0.1}
    \pgfmathsetmacro{\cubedist}{0.4}
    \def\rounding{0.2pt};
    \foreach \i in {10,50,...,360}
    { \tdplotsetrotatedcoords{\i}{00}{0}
        \begin{scope}[tdplot_rotated_coords]
            \path[get projections];
            %\draw (-\cubedist,0,0) -- (-2,0,0)  node{\xproj,\yproj,\zproj};
            % http://tex.stackexchange.com/questions/12020/what-is-the-easiest-way-to-draw-3d-cube-with-tikz
            \ifnum\yproj=1
             \draw[rounded corners = \rounding,fill=black!70] (-\cubedist,0,0) -- ++(0,0,-\cubez) -- ++(0,-\cubey,0) -- ++(0,0,\cubez) -- cycle;
            \else
             \draw[rounded corners = \rounding,fill=black!70] (-\cubedist-\cubex,0,0) -- ++(0,0,-\cubez) -- ++(0,-\cubey,0) -- ++(0,0,\cubez) -- cycle;
            \fi
            \ifnum\xproj=1             
             \draw[rounded corners = \rounding,fill=black!70] (-\cubedist,-\cubey,0) -- ++(-\cubex,0,0) -- ++(0,0,-\cubez) -- ++(\cubex,0,0) -- cycle;
            \else
             \draw[rounded corners = \rounding,fill=black!70] (-\cubedist,0,0) -- ++(-\cubex,0,0) -- ++(0,0,-\cubez) -- ++(\cubex,0,0) -- cycle;
            \fi
            \draw[rounded corners = \rounding,fill=black!70] (-\cubedist,0,-\cubez) -- ++(-\cubex,0,0) -- ++(0,-\cubey,0) -- ++(\cubex,0,0) -- cycle;
        \end{scope}
    }

\end{tikzpicture}

\end{document}

enter image description here

I personally would prefer to dial a positive theta and positive lengths, then the completion of the figure is arguably much more straightforward. Then, in order to come closer to the OpenSCAD syntax, I stored the definition of the cuboid in a style (this is certainly not the first post defining a cuboid pic), in which there is an attempt of automatic shading, less sophisticated than what pstricks has to offer and certainly much less less convincing than asymptote. This pic can be used as

 \path (<coordinate>) pic{cuboid={x=<x>,y=<y>,z=<z>}};

and certainly can be made more flexible, if needed (but this flexibility comes with a slightly longer code). Note also that if you want the cuboids to go closer to each other, some care has to be taken in which order they are drawn: you need to draw those which are in be back first.

\documentclass[tikz,border=7pt]{standalone}
\usepackage{tikz-3dplot}
\tikzset{get projections/.style={insert path={%
let \p1=(1,0,0),\p2=(0,1,0)  in 
[/utils/exec={\pgfmathtruncatemacro{\xproj}{sign(\x1)}\xdef\xproj{\xproj}
\pgfmathtruncatemacro{\yproj}{sign(\x2)}\xdef\yproj{\yproj}
\pgfmathtruncatemacro{\zproj}{sign(cos(\tdplotmaintheta))}\xdef\zproj{\zproj}}]}},
pics/cuboid/.style={code={\tikzset{cuboid/.cd,#1}
 \path[get projections];
 \ifnum\yproj=-1
  \draw let \p1=(1,0,0),\n1={atan2(\y1,\x1)} 
  in [/utils/exec=\pgfmathsetmacro{\mybr}{70-20*sin(\n1)}]
  [rounded corners = \rounding,fill=black!\mybr] 
     (0,-\cubey/2,0) -- ++(0,0,\cubez) -- ++(0,\cubey,0) -- ++(0,0,-\cubez) -- cycle;
 \else
  \draw let \p1=(1,0,0),\n1={atan2(\y1,\x1)} 
  in [/utils/exec=\pgfmathsetmacro{\mybr}{70+20*sin(\n1)}]
  [rounded corners = \rounding,fill=black!\mybr] 
     (0+\cubex,-\cubey/2,0) -- ++(0,0,\cubez) -- ++(0,\cubey,0) -- ++(0,0,-\cubez) -- cycle;
 \fi
 \ifnum\xproj=-1             
  \draw let \p1=(0,1,0),\n1={atan2(\y1,\x1)} 
  in [/utils/exec=\pgfmathsetmacro{\mybr}{70+20*sin(\n1)}]
  [rounded corners = \rounding,fill=black!\mybr] 
     (0,\cubey/2,0) -- ++(\cubex,0,0) -- ++(0,0,\cubez) -- ++(-\cubex,0,0) -- cycle;
 \else
  \draw let \p1=(0,1,0),\n1={atan2(\y1,\x1)} 
  in [/utils/exec=\pgfmathsetmacro{\mybr}{70-20*sin(\n1)}]
  [rounded corners = \rounding,fill=black!\mybr] 
     (0,-\cubey/2,0) -- ++(\cubex,0,0) -- ++(0,0,\cubez) -- ++(-\cubex,0,0) -- cycle;
 \fi
  \draw let \p1=(0,0,1),\n1={atan2(\y1,\x1)} 
  in [/utils/exec=\pgfmathsetmacro{\mybr}{70-20*sin(\n1)}]
   [rounded corners = \rounding,fill=black!\mybr]
     (0,-\cubey/2,\cubez) -- ++(\cubex,0,0) -- ++(0,\cubey,0) --
     ++(-\cubex,0,0) -- cycle;}},
 cuboid/.cd,x/.estore in=\cubex,y/.estore in=\cubey,z/.estore
 in=\cubez,rounding/.estore in=\rounding,rounding=0.2pt  
}
%%%%%%%%%%%
\definecolor{bred}{RGB}{151,73,59}
\begin{document}

\tdplotsetmaincoords{60}{-50}

\begin{tikzpicture}[tdplot_main_coords,cuboid/.cd,x=4,y=0.4,z=0.4]
    \pgfmathsetmacro{\cubedist}{0.8}
    \fill[gray!40] plot[smooth,variable=\t,domain=0:360,samples=51] 
        ({(\cubex+\cubedist)*cos(\t)},
        {(\cubex+\cubedist)*sin(\t)},-\cubez);
    \draw[ultra thin,left color=gray!60,right color=gray!80,middle color=gray!20] 
        plot[smooth,variable=\t,domain=\tdplotmainphi:\tdplotmainphi+180] 
        ({(\cubex+\cubedist)*cos(\t)},
        {(\cubex+\cubedist)*sin(\t)},0)
        -- 
        plot[smooth,variable=\t,domain=\tdplotmainphi+180:\tdplotmainphi] 
        ({(\cubex+\cubedist)*cos(\t)},
        {(\cubex+\cubedist)*sin(\t)},-\cubez) -- cycle;
    \draw[ultra thin,left color=gray!80,right color=gray!60,middle color=gray!20] 
        plot[smooth,variable=\t,domain=\tdplotmainphi:\tdplotmainphi-180] 
        ({(\cubex+\cubedist)*cos(\t)},
        {(\cubex+\cubedist)*sin(\t)},0)
        -- 
        plot[smooth,variable=\t,domain=\tdplotmainphi-180:\tdplotmainphi] 
        ({(\cubex+\cubedist)*cos(\t)},
        {(\cubex+\cubedist)*sin(\t)},-\cubez) -- cycle;
    \draw[ultra thin,fill=gray!10,even odd rule] plot[smooth,variable=\t,domain=0:360,samples=51] 
        ({(\cubex+\cubedist)*cos(\t)},
        {(\cubex+\cubedist)*sin(\t)},0)
        plot[smooth,variable=\t,domain=0:360,samples=51] 
        ({0.98*(\cubex+\cubedist)*cos(\t)},
        {0.98*(\cubex+\cubedist)*sin(\t)},0);   
    \foreach \i in {50,90,130,170,10,-30,-70,...,-150} %<-note that this list depends on phi
    { \tdplotsetrotatedcoords{\i}{00}{0}
        \begin{scope}[tdplot_rotated_coords]
          \path (\cubedist,0,0) pic{cuboid};  
        \end{scope}
    }
    \begin{scope}[canvas is xy plane at z=\cubez]
        \fill[bred,even odd rule] circle[radius=\cubex]
        circle[radius=0.6*\cubex];
        \draw[ultra thin] (0:0.6*\cubex) -- (0:\cubex)  (180:0.6*\cubex) -- (180:\cubex);
    \end{scope}
\end{tikzpicture}
\end{document}

enter image description here

As usual, the view can be rotated, and the appropriate ordering of the cuboids can be automatized.

\documentclass[tikz,border=7pt]{standalone}
\usepackage{tikz-3dplot}
\tikzset{get projections/.style={insert path={%
let \p1=(1,0,0),\p2=(0,1,0)  in 
[/utils/exec={\pgfmathtruncatemacro{\xproj}{sign(\x1)}\xdef\xproj{\xproj}
\pgfmathtruncatemacro{\yproj}{sign(\x2)}\xdef\yproj{\yproj}
\pgfmathtruncatemacro{\zproj}{sign(cos(\tdplotmaintheta))}\xdef\zproj{\zproj}}]}},
pics/cuboid/.style={code={\tikzset{cuboid/.cd,#1}
 \path[get projections];
 \ifnum\yproj=-1
  \draw let \p1=(1,0,0),\n1={atan2(\y1,\x1)} 
  in [/utils/exec=\pgfmathsetmacro{\mybr}{70-20*sin(\n1)}]
  [rounded corners = \rounding,fill=black!\mybr] 
     (0,-\cubey/2,0) -- ++(0,0,\cubez) -- ++(0,\cubey,0) -- ++(0,0,-\cubez) -- cycle;
 \else
  \draw let \p1=(1,0,0),\n1={atan2(\y1,\x1)} 
  in [/utils/exec=\pgfmathsetmacro{\mybr}{70+20*sin(\n1)}]
  [rounded corners = \rounding,fill=black!\mybr] 
     (0+\cubex,-\cubey/2,0) -- ++(0,0,\cubez) -- ++(0,\cubey,0) -- ++(0,0,-\cubez) -- cycle;
 \fi
 \ifnum\xproj=-1             
  \draw let \p1=(0,1,0),\n1={atan2(\y1,\x1)} 
  in [/utils/exec=\pgfmathsetmacro{\mybr}{70+20*sin(\n1)}]
  [rounded corners = \rounding,fill=black!\mybr] 
     (0,\cubey/2,0) -- ++(\cubex,0,0) -- ++(0,0,\cubez) -- ++(-\cubex,0,0) -- cycle;
 \else
  \draw let \p1=(0,1,0),\n1={atan2(\y1,\x1)} 
  in [/utils/exec=\pgfmathsetmacro{\mybr}{70-20*sin(\n1)}]
  [rounded corners = \rounding,fill=black!\mybr] 
     (0,-\cubey/2,0) -- ++(\cubex,0,0) -- ++(0,0,\cubez) -- ++(-\cubex,0,0) -- cycle;
 \fi
  \draw let \p1=(0,0,1),\n1={atan2(\y1,\x1)} 
  in [/utils/exec=\pgfmathsetmacro{\mybr}{70-20*sin(\n1)}]
   [rounded corners = \rounding,fill=black!\mybr]
     (0,-\cubey/2,\cubez) -- ++(\cubex,0,0) -- ++(0,\cubey,0) --
     ++(-\cubex,0,0) -- cycle;}},
 cuboid/.cd,x/.estore in=\cubex,y/.estore in=\cubey,z/.estore
 in=\cubez,rounding/.estore in=\rounding,rounding=0.2pt  
}
%%%%%%%%%%%
\definecolor{bred}{RGB}{151,73,59}
\begin{document}
\pgfmathsetmacro{\xmin}{0}
\pgfmathsetmacro{\xmax}{0}
\pgfmathsetmacro{\ymin}{0}
\pgfmathsetmacro{\ymax}{0}
\foreach \X in {5,15,...,355}
{\tdplotsetmaincoords{60}{\X}
\begin{tikzpicture}[tdplot_main_coords,cuboid/.cd,x=4,y=0.4,z=0.4]
    \ifdefined\figbb\relax
    \path[tdplot_screen_coords] \figbb;
    \fi    
    \pgfmathsetmacro{\cubedist}{0.8}
    \fill[gray!40] plot[smooth,variable=\t,domain=0:360,samples=51] 
        ({(\cubex+\cubedist)*cos(\t)},
        {(\cubex+\cubedist)*sin(\t)},-\cubez);
    \draw[ultra thin,left color=gray!60,right color=gray!80,middle color=gray!20] 
        plot[smooth,variable=\t,domain=\tdplotmainphi:\tdplotmainphi+180] 
        ({(\cubex+\cubedist)*cos(\t)},
        {(\cubex+\cubedist)*sin(\t)},0)
        -- 
        plot[smooth,variable=\t,domain=\tdplotmainphi+180:\tdplotmainphi] 
        ({(\cubex+\cubedist)*cos(\t)},
        {(\cubex+\cubedist)*sin(\t)},-\cubez) -- cycle;
    \draw[ultra thin,left color=gray!80,right color=gray!60,middle color=gray!20] 
        plot[smooth,variable=\t,domain=\tdplotmainphi:\tdplotmainphi-180] 
        ({(\cubex+\cubedist)*cos(\t)},
        {(\cubex+\cubedist)*sin(\t)},0)
        -- 
        plot[smooth,variable=\t,domain=\tdplotmainphi-180:\tdplotmainphi] 
        ({(\cubex+\cubedist)*cos(\t)},
        {(\cubex+\cubedist)*sin(\t)},-\cubez) -- cycle;
    \draw[ultra thin,fill=gray!10,even odd rule] plot[smooth,variable=\t,domain=0:360,samples=51] 
        ({(\cubex+\cubedist)*cos(\t)},
        {(\cubex+\cubedist)*sin(\t)},0)
        plot[smooth,variable=\t,domain=0:360,samples=51] 
        ({0.98*(\cubex+\cubedist)*cos(\t)},
        {0.98*(\cubex+\cubedist)*sin(\t)},0);
    \pgfmathtruncatemacro{\tmpA}{10+\tdplotmainphi+90-mod(360+\tdplotmainphi+90,40)}        
    \pgfmathtruncatemacro{\tmpB}{\tmpA+40}      
    \pgfmathtruncatemacro{\tmpC}{\tmpA+160}     
    \pgfmathtruncatemacro{\tmpD}{\tmpA-40}      
    \pgfmathtruncatemacro{\tmpE}{\tmpA-80}      
    \pgfmathtruncatemacro{\tmpF}{\tmpA-160}     
    \foreach \i in {\tmpA,\tmpB,...,\tmpC,\tmpD,\tmpE,...,\tmpF} %<-automatized
    { \tdplotsetrotatedcoords{\i}{00}{0}
        \begin{scope}[tdplot_rotated_coords]
          \path (\cubedist,0,0) pic{cuboid};  
        \end{scope}
    }
    \begin{scope}[canvas is xy plane at z=\cubez]
        \fill[bred,even odd rule] circle[radius=\cubex]
        circle[radius=0.6*\cubex];
        \draw[ultra thin] (0:0.6*\cubex) -- (0:\cubex)  (180:0.6*\cubex) -- (180:\cubex);
    \end{scope}
    \path let \p1=(current bounding box.south west),
    \p2=(current bounding box.north east)
    in \pgfextra{%
    \pgfmathsetmacro{\xmin}{min(\x1,\xmin)}
    \pgfmathsetmacro{\xmax}{max(\x2,\xmax)}
    \pgfmathsetmacro{\ymin}{min(\y1,\ymin)}
    \pgfmathsetmacro{\ymax}{max(\y2,\ymax)}
    \xdef\xmin{\xmin pt}
    \xdef\xmax{\xmax pt}    
    \xdef\ymin{\ymin pt}
    \xdef\ymax{\ymax pt}    
    };  
\end{tikzpicture}}
\makeatletter               
\edef\figbb{(\xmin,\ymin) rectangle (\xmax,\ymax)}
\immediate\write\@mainaux{\xdef\string\figbb{\figbb}\relax}
\makeatother
\end{document}

enter image description here

4
  • That is amazing. Thank you! Jul 15, 2019 at 2:02
  • As an aside, is it possible to do this with asymptote or pgfplots? My preferred environment is TikZ, as that's the one I've invested in learning, but I didn't realise this would be possibly in asymptote, and I would be very interested in learning some more techniques to get these sorts of results. Jul 15, 2019 at 2:04
  • @FortuneCookie I believe it is but I am not very experienced with it. Some time ago I played with it, see here. asymptote has a 3d engine, several things become simpler (but for such things you won't get vector graphics easily).
    – user121799
    Jul 15, 2019 at 4:28
  • Thanks! The revolving door is enough to get me started. Jul 16, 2019 at 0:59

You must log in to answer this question.

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