# Custom shapes in TikZ

I am trying to define a new shape in TikZ following the instructions of section 75.5 of the manual. Reading the question Drawing mechanical systems in LaTeX, I started to think that a "mechanical" library is really missing in TikZ, and it shouldn't, because it's useful just as libraries for circuits or diagrams. So I started in reproducing the symbol of a mechanical constraint:

(forget the vertical dashed line in the center) with the aim to recreate a set of symbols useful to draw mechanical systems and structures. That is what I've done:

\documentclass[a4paper]{article}

\usepackage{pgfplots}
\makeatletter
\pgfdeclareshape{carr}{%
\inheritsavedanchors[from=circle]
\inheritanchor[from=circle]{center}
\inheritanchor[from=circle]{north}
\inheritanchor[from=circle]{south}
\inheritanchor[from=circle]{east}
\inheritanchor[from=circle]{west}
\backgroundpath{%
% center
\centerpoint \pgf@xa=\pgf@x   \pgf@ya=\pgf@y
% west triangle corner
\pgf@xb=-2\pgf@x
% east triangle corner
\pgf@xc=4\pgf@x
% draw triangle..
\pgfpathmoveto{\pgfpoint{\pgf@xa}{\pgf@ya}}%
\pgfpathlineto{\pgfpoint{\pgf@xb}{\pgf@yb}}%
\pgfpathlineto{\pgfpoint{\pgf@xc}{\pgf@yc}}%
\pgfpathclose
% central circle (from pgflibraryshapes.geometric.code.tex)..
\pgfutil@tempdima=\pgf@x%
\pgfutil@tempdimb=\pgf@y%
\pgfmathsetlength{\pgf@xb}{\pgfkeysvalueof{/pgf/outer xsep}}%
\pgfmathsetlength{\pgf@yb}{\pgfkeysvalueof{/pgf/outer ysep}}%
\pgfpathellipse{\centerpoint}{\pgfqpoint{\pgfutil@tempdimb}{0pt}}{\pgfqpoint{0pt}{\pgfutil@tempdimb}}%
% and lower circles
\pgf@xa=1pt % the radius:should be a parameter
\pgfpathcircle{\pgfpoint{\pgfutil@tempdima}{\pgfutil@tempdimb}}{\pgf@xa}
\pgfpathcircle{\pgfpoint{\pgfutil@tempdima}{\pgfutil@tempdimb}}{\pgf@xa}
}}
\makeatother

\begin{document}
\begin{tikzpicture}
\node[shape=carr,draw] at (3,0) {a};
\node[shape=carr,draw] at (5,0) {}; % missing triangle
\draw[shape=carr] (7,0); % nothing shown
\end{tikzpicture}
\end{document}


Looking at the code of other libraries, this seems to be the right way to procede, but correct me if I'm wrong. As you can see from the resulting pdf, the two lower circles in the figures are misplaced a lot, and I don't understand why. The code used to draw the upper circle is taken from the definition of the ellipse in tikz. In addition, I don't know why, if I use an empty label in the node, the rectangle is missing. The last line of code (\draw[shape=carr....) is just an experiment. I would like to be able to write such a thing and see in the pdf just the reproduced shape, but I don't know if this is possible.

How to change the code to achieve the desider result? Do you think that this is just a waste of time? Suggestions on any direction are welcomed

-
Why do you need constraints? Can't you just place the stuff you want to the right places? –  msmechanized Aug 3 '12 at 8:58
@msmechanized I don't fully understand what you mean. The constraints are used in mechanical diagrams like the one in thi figure to define the static (and cinematic) behaviour of a body subject to external forces. So I don't need constraints in defining the position of the bodies, but I need to "draw" them. Each of them is linked to a symbol –  Spike Aug 3 '12 at 9:56
@zeroth thank you, just done! ;) –  Spike Mar 18 '13 at 9:43
@Spike Related At latex-community.org TikZ Library for Structural Analysis –  texenthusiast Mar 18 '13 at 10:09
@texenthusiast very very very very nice. Thank you –  Spike Mar 18 '13 at 11:30

Declaring shapes is one of the most useful features of the tikz/pgf library. It can be extended by endless choice and really is a good idea.

The problem with the shapes is the difficulties one has to go through to get it to work. This is due to the low-level coding that is necessary and essentially a good bookkeeping of variables is the problem. If one is not used to low-level variable reassignment this can become a cumbersome and even a task which cannot be completed without coding more than the basic layer offers.

This is also the problem of your code.

\pgfdeclareshape{carr}{%
\inheritsavedanchors[from=circle]
\inheritanchor[from=circle]{center}
\inheritanchor[from=circle]{north}
\inheritanchor[from=circle]{south}
\inheritanchor[from=circle]{east}
\inheritanchor[from=circle]{west}


All this is fine, just keep it, you may realise that your east, west, etc. is not correctly placed. Thus you should add your own.

  \backgroundpath{%
% center
\centerpoint
\pgf@xa=\pgf@x
\pgf@ya=\pgf@y


This is also good, you acquire the center coordinates. However, the center coordinates only have a value if there is text in the node. They are defined as:
\pgf@x=.5\wd\pgfnodeparttextbox
\pgf@y=.5\ht\pgfnodeparttextbox
\advance\pgf@y by-.5\dp\pgfnodeparttextbox

Thus if no text is present they are both 0pt. This tells you why it didn't show if no node text is supplied.

    % west triangle corner
\pgf@yb=-5\pgf@y
\centerpoint
\pgf@xb=-2\pgf@x


Your problem here is that \radius is actually a dimension. You do not save this value in any way. You need to use \radius as a regular dimension:
\pgf@x=\radius

This means that what ever you are saving to \pgf@yb you have no idea of what is (or actually it is \pgf@y from \centerpoint, but that is another matter).

    % east triangle corner
\pgf@yc=-5\pgf@y
\centerpoint
\pgf@xc=4\pgf@x


same problem here. This will not work.

    % draw triangle..
\pgfpathmoveto{\pgfpoint{\pgf@xa}{\pgf@ya}}%
\pgfpathlineto{\pgfpoint{\pgf@xb}{\pgf@yb}}%
\pgfpathlineto{\pgfpoint{\pgf@xc}{\pgf@yc}}%
\pgfpathclose


This is fine

    % central circle (from pgflibraryshapes.geometric.code.tex)..


Here is another mistake. Let's go through what \pgf@process does.

Lets consider the following function:

\def\pgfcalc{\pgf@xb=2pt\pgf@x=\pgf@xb}


\pgfcalc will actually define both \pgf@xb and \pgf@x. In many cases you are only interested in the final result, which is \pgf@x and \pgf@y. In such case one will invoke \pgfcalc by \pgf@process.

\pgf@xb=1pt
\pgfcalc
% Here \pgf@xb and \pgf@x are set by \pgfcalc
% So \pgf@xb and \pgf@x are both 2pt
\pgf@xb=1pt
\pgf@process{\pgfcalc}
% Here ONLY \pgf@x is set. \pgf@xb is still 1pt


This means that you can only call \pgf@process on a macro and not a dimension (as \radius is). The code you refer to have \radius defined as an anchor which is a macro. You could have done it on \centerpoint if \centerpoint were to change any other than \pgf@x and \pgf@y.

For further information on the purposes see the manual.

Lets move on...

    \pgfutil@tempdima=\pgf@x%
\pgfutil@tempdimb=\pgf@y%
\pgfmathsetlength{\pgf@xb}{\pgfkeysvalueof{/pgf/outer xsep}}%
\pgfmathsetlength{\pgf@yb}{\pgfkeysvalueof{/pgf/outer ysep}}%
\pgfpathellipse{\centerpoint}{\pgfqpoint{\pgfutil@tempdimb}{0pt}}{\pgfqpoint{0pt}{\pgfutil@tempdimb}}%
% and lower circles
\pgf@xa=1pt % the radius:should be a parameter
\pgfpathcircle{\pgfpoint{\pgfutil@tempdima}{\pgfutil@tempdimb}}{\pgf@xa}
\pgfpathcircle{\pgfpoint{\pgfutil@tempdima}{\pgfutil@tempdimb}}{\pgf@xa}
}}


This final part is actually ok, however, it is cumbersome to read and one can easily make mistakes. I would partition it a bit more.

## A fast solution

This solution has been made so that you can work more with it. There are many options that you need to fine-tune and post-process.

It is up to you to complete with the top circle etc.

\documentclass{article}

\usepackage{tikz}
\makeatletter
\pgfdeclareshape{carr}{%
\inheritsavedanchors[from=circle]
\inheritanchor[from=circle]{center}
\inheritanchor[from=circle]{north}
\inheritanchor[from=circle]{south}
\inheritanchor[from=circle]{east}
\inheritanchor[from=circle]{west}
\backgroundpath{%
% Radius is also containing the "minimum width" and "minimum height"
% This ensures that even with no text the shape will be drawn.
% Unless of course that min are set to 0pt
% So no need to check for that
\pgfutil@tempdima=\pgf@x%

% west triangle corner "b"
\pgf@xb=-3\pgf@x%
\pgf@yb=-4\pgf@x%
% east triangle corner "c"
\pgf@xc= 3\pgf@x%
\pgf@yc=-4\pgf@x%

% If text is present shift shape to center
% You need to shift more, but to get the idea
\centerpoint

% Save centerpoint in "a" (top triangle point)
\pgf@xa=\pgf@x
\pgf@ya=\pgf@y

% Below are good for debugging purposes.
%\message{^^JTop : \the\pgf@xa,\the\pgf@ya}
%\message{^^JWest: \the\pgf@xb,\the\pgf@yb}
%\message{^^JEast: \the\pgf@xc,\the\pgf@yc}
%\message{^^JCent: \the\pgf@x,\the\pgf@y}

% draw triangle..
\pgfpathmoveto{\pgfpoint{\pgf@xa}{\pgf@ya}}%
\pgfpathlineto{\pgfpoint{\pgf@xb}{\pgf@yb}}%
\pgfpathlineto{\pgfpoint{\pgf@xc}{\pgf@yc}}%
\pgfpathclose

% The radius of the small circles
% Read in from option TODO
\pgfutil@tempdimb=3pt

% Move top triangle to head circle
% Move west triangle corner to west circle center
% For handling line thickness if you wish "edge touch" and not "overlap"
% Move east triangle corner to east circle center
% For handling line thickness if you wish "edge touch" and not "overlap"

% This saves underlying "stuff" when you have the explicit \pgfqpoint and is thus a little faster
\edef\pgf@marshal{%
\noexpand\pgfpathcircle{%
\noexpand\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}
{\the\pgfutil@tempdimb}%
\noexpand\pgfpathcircle{%
\noexpand\pgfqpoint{\the\pgf@xb}{\the\pgf@yb}}
{\the\pgfutil@tempdimb}%
\noexpand\pgfpathcircle{%
\noexpand\pgfqpoint{\the\pgf@xc}{\the\pgf@yc}}
{\the\pgfutil@tempdimb}%
}\pgf@marshal
}}
\makeatother

\begin{document}
\begin{tikzpicture}
\node[shape=carr,draw] at (3,0) {a};
\node[shape=carr,draw] at (5,0) {}; % missing triangle (not anymore)
% Your \draw example will never work! shapes are nodes, you need a node to assign the shape!
\end{tikzpicture}
\end{document}


Here is the end result:

Good luck battling those shapes! :)

-
Just great! Thank you a lot. I'll wait a bit to mark the answer as accepted. I'll study the code in the next days. –  Spike Aug 3 '12 at 18:23
@Spike that is fine. Good study! :) –  zeroth Aug 3 '12 at 19:19
Hi, I'm working on it and I reached a good result (I think). There is a typo during the explanation: \def\pgfcalc{\pgf@xb=2pt\pgf@x=\pgf@x} should be \def\pgfcalc{\pgf@xb=2pt\pgf@x=\pgf@xb}, right? I don't understand what you mean with "% Read in from option TODO" while defining the circles radius. Can you explain it better? I'll post the final version soon as an answer. Thanks a lot –  Spike Aug 21 '12 at 16:02
@Spike yes that is a typo. The TODO thing is because at the moment the circle is the same size no matter the triangle. That is probably not intended, and you can create options to retrieve any user defined sizes and the likes. –  zeroth Aug 21 '12 at 16:31

Starting from the @zeroth answer, I've added some useful things, so I'm sharing what could be the "final result". I'll comment the differences:

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{patterns}

\tikzstyle{ground}=[fill,pattern=north east lines,draw=none,%
minimum width=1cm,minimum height=0.3cm]


This tikzstyle is just for a future test (see the end of the example)

\makeatletter
\pgfdeclareshape{carr}{%
\inheritsavedanchors[from=circle]
% getting half the value of \radius (the last saved "anchor")
\pgf@x=.5\pgf@x}


Here I created a new dimension that is always an half of the last "saved dimension", in this case \radius (see Usage of a saved dimension in another saved dimension in a \pgfdeclareshape command. This simply because circles where too big (personal) and then I wanted to reduce their standard size. Maybe is better to copy the definition of the \radius before this code to avoid problems due to changes in the original code of the circle.

% redefining anchors starting from "circle" because of the different radius
\anchorborder{
\pgf@xa=\pgf@x%
\pgf@ya=\pgf@y%
\edef\pgf@marshal{%
\noexpand\pgfpointborderellipse
{\noexpand\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}
}%
\pgf@marshal%
\pgf@xa=\pgf@x%
\pgf@ya=\pgf@y%
\centerpoint%
}
% Taking some useful anchors from "circle" and defining others of them
\inheritanchor[from=circle]{center}
\inheritanchor[from=circle]{mid}
\inheritanchor[from=circle]{base}
\anchor{north west}{
\centerpoint
}
\anchor{south west}{
\centerpoint
}
\anchor{north east}{
\centerpoint
}
\anchor{south east}{
\centerpoint
}
\anchor{bottom}{% added on the baseline of the shape
\centerpoint
}


Because of the difference between \radius and \halfradius I had to redefine some anchors and the \anchorborder. In this whay when you want to draw a line from a node shaped carr, the line would start at the border of the upper circle. In addition I defined a new anchor at the baseline of the shape, useful to place the ground (see figure).

  \backgroundpath{%
% Radius is also containing the "minimum width" and "minimum height"
% This ensures that even with no text the shape will be drawn.
% Unless of course that min are set to 0pt
% So no need to check for that
\pgfutil@tempdima=\pgf@x% maybe useless now

% west triangle corner "b"
\pgf@xb=-2\pgf@x%
\pgf@yb=-3\pgf@x%
% east triangle corner "c"
\pgf@xc= 2\pgf@x%
\pgf@yc=-3\pgf@x%

% If text is present shift shape to center
% You need to shift more, but to get the idea
\centerpoint

% Save centerpoint in "a" (top triangle point)
\pgf@xa=\pgf@x
\pgf@ya=\pgf@y

% Below are good for debugging purposes.
%\message{^^JTop : \the\pgf@xa,\the\pgf@ya}
%\message{^^JWest: \the\pgf@xb,\the\pgf@yb}
%\message{^^JEast: \the\pgf@xc,\the\pgf@yc}
%\message{^^JCent: \the\pgf@x,\the\pgf@y}
%\message{^^JR: \the\pgfutil@tempdima}


This is just the same

      % draw triangle (considering upper circle)..
% (0.5547 0.832)
% starting from upper left edge
\pgfpathmoveto{\pgfpoint{\pgf@xa}{\pgf@ya}}%
\pgfpathlineto{\pgfpoint{\pgf@xb}{\pgf@yb}}%
\pgfpathlineto{\pgfpoint{\pgf@xc}{\pgf@yc}}%
% changing to upper right edge...
\pgfpathlineto{\pgfpoint{\pgf@xa}{\pgf@ya}}%
% ...and back to upper left to close the path
\pgfpathmoveto{\pgfpoint{\pgf@xa}{\pgf@ya}}%
\pgfpathclose%


To delete the little part of the triangle in the upper circle I started to draw the path from the border of the circle. To do that I modified as I needed the values of \pgf@xa and \pgf@ya because using a different length (\pgf@xd, for example) gave an undefined control sequence error.

      % Restore centerpoint in "a" (top triangle point) and the value of
% \pgfutil@tempdima

% The radius of the small circles
% Read in from option TODO


Here the radius of the small circles is set to \halfradius. In this way the presence of a label or a command like \huge changes coherently the dimension of the whole shape

%      % Move top triangle to head circle


It looks to me that when this is off, the label is better centered in the upper circle, but I could be wrong.

      % Move west triangle corner to west circle center
% For handling line thickness if you wish "edge touch" and not "overlap"
% Move east triangle corner to east circle center
%% For handling line thickness if you wish "edge touch" and not "overlap"

% This saves underlying "stuff" when you have the explicit \pgfqpoint and is thus a little faster
\edef\pgf@marshal{%
\noexpand\pgfpathcircle{%
\noexpand\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}
{\the\pgfutil@tempdimb}%
\noexpand\pgfpathcircle{%
\noexpand\pgfqpoint{\the\pgf@xb}{\the\pgf@yb}}
{\the\pgfutil@tempdimb}%
\noexpand\pgfpathcircle{%
\noexpand\pgfqpoint{\the\pgf@xc}{\the\pgf@yc}}
{\the\pgfutil@tempdimb}%
}\pgf@marshal
}}
\makeatother

\begin{document}
\begin{tikzpicture}
\draw (0,0) -- (4,0);
\node[shape=carr,draw] (a) at (1,0) {O}; % the shape changes as a whole
\node[shape=carr,draw] (b) at (3,0) {}; % standard
{\Huge \node[shape=carr,draw] (c) at (5,0) {};} % good
\end{tikzpicture}\\[1cm]
\begin{tikzpicture}
\coordinate (a) at (0,0);
\coordinate (b) at (2,2);
\coordinate (c) at (5,2);
\node[draw,carr,rotate=22] (A) at (a){};
\node[draw,carr,fill=red] (B) at (b){}; % fill works (obvious?)
\node[draw,carr,pattern=fivepointed stars] (C) at (c){}; % pattern works
\node[ground,anchor=north] (g1) at (C.bottom){}; % the new anchor is ok
\draw (g1.north west) -- (g1.north east);
\draw[thick] (A) -- (B) -- (C); % good connections (\halfradius)
\draw[thick] (A.north) |- (B.west);
\end{tikzpicture}
\end{document}


Result:

-
I had not responded to your post here. It looks really good. The reason that you get undefined control sequence with pgf@xd is that it really is not defined. Only a, b and c are defined to be used for processing. –  zeroth Dec 27 '12 at 18:32