You should use (wall2.190) -- ($(M1.north east)!(wall2.190)!(M1.north west)$)
(notice the use of 190
instead of 170
). The first expression specifies the starting point of the spring, which in this case is in the bottom left of the wall. The <node>.<number>
anchors specify points on the edge of a node, with the number specifying the angle at which the anchor is placed. 0
is the middle of the right edge, 90
is the middle of the top, 170
is slightly higher than the middle of the left edge, and 190
is slightly lower than the middle of the left edge. The second expression specifies the end point of the spring, which is at the projection of the starting point on the line from the top left of the rectangle to the top right (($(A)!(P)!(B)$)
is the projection of P
on AB
).
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc,patterns,decorations.pathmorphing,decorations.markings}
\begin{document}
\begin{tikzpicture}[M1/.style={rectangle,draw=black,minimum size=2cm,thick}]
\tikzstyle{spring}=[thick,decorate,decoration={zigzag,pre length=0.3cm,post length=0.3cm,segment length=5}]
\tikzstyle{ground}=[fill,pattern=north east lines,draw=none,minimum width=0.75cm,minimum height=0.3cm]
\node [M1] (M1) {};
\node (wall1) [ground, minimum width=3cm,yshift=-3cm] {};
\draw (wall1.north west) -- (wall1.north east);
\draw [spring] (wall1.170) -- ($(M1.south east)!(wall1.170)!(M1.south west)$) node[pos=.5,left] {$k_2$};
\draw [spring] (wall1.10) -- ($(M1.south west)!(wall1.10)!(M1.south east)$) node[pos=.5,right] {$k_3$};
\node (wall2) [ground, minimum width=3cm,yshift=3cm] {};
\draw (wall2.south west) -- (wall2.south east);
\draw [spring] (wall2.190) -- ($(M1.north east)!(wall2.190)!(M1.north west)$) node[pos=.5,left] {$k_1$};
\draw [-latex,thick] (1.5,0) -- (1.5,1) node [pos=.5,right] {$x$};
\filldraw circle (.05) node [pos=.5,below] {\tiny $CG$};
\node [rotate=90,above] at (M1.west) {Mass,$m$};
\node at (0,-3.5) {(a)};
\begin{scope}[xshift=5cm]
\node [M1] (M1) {};
\node (wall1) [ground, minimum width=3cm,yshift=-3cm] {};
\draw (wall1.north west) -- (wall1.north east);
\draw [spring] (wall1) -- ($(M1.south east)!(wall1)!(M1.south west)$) node[pos=.5,left] {$k_2^*$};
\node (wall2) [ground, minimum width=3cm,yshift=3cm] {};
\draw (wall2.south west) -- (wall2.south east);
\draw [spring] (wall2) -- ($(M1.north east)!(wall2)!(M1.north west)$) node[pos=.5,left] {$k_1$};
\draw [-latex,thick] (1.5,0) -- (1.5,1) node [pos=.5,right] {$x$};
\filldraw circle (.05) node [pos=.5,below] {\tiny $CG$};
\node [rotate=90,above] at (M1.west) {Mass,$m$};
\node at (0,-3.5) {(b)};
\end{scope}
\end{tikzpicture}
\end{document}
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.
Commenting 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
\radius
\pgf@yb=-5\pgf@y
\centerpoint
\advance\pgf@yb by\pgf@y
\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
\radius
\pgf@yc=-5\pgf@y
\centerpoint
\advance\pgf@yc by\pgf@y
\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)..
\pgf@process{\radius}
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}}%
\advance\pgfutil@tempdima by-\pgf@xb%
\advance\pgfutil@tempdimb by-\pgf@yb%
\pgfpathellipse{\centerpoint}{\pgfqpoint{\pgfutil@tempdimb}{0pt}}{\pgfqpoint{0pt}{\pgfutil@tempdimb}}%
% and lower circles
\pgf@xa=1pt % the radius:should be a parameter
\pgfutil@tempdima=\pgf@xb \advance\pgfutil@tempdima by\pgf@xa
\pgfutil@tempdimb=\pgf@yb \advance\pgfutil@tempdimb by-\pgf@xa
\pgfpathcircle{\pgfpoint{\pgfutil@tempdima}{\pgfutil@tempdimb}}{\pgf@xa}
\pgfutil@tempdima=\pgf@xb \advance\pgfutil@tempdima by-\pgf@xa
\pgfutil@tempdimb=\pgf@yb \advance\pgfutil@tempdimb by-\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{%
% Save radius to x
\pgf@x=\radius
% 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
% Save radius
\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
\advance\pgf@xb by\pgf@x
\advance\pgf@yb by\pgf@y
\advance\pgf@xc by\pgf@x
\advance\pgf@yc by\pgf@y
% 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
\advance\pgf@ya by.25\pgfutil@tempdimb
% Move west triangle corner to west circle center
\advance\pgf@xb by 1.5\pgfutil@tempdima
\advance\pgf@yb by -\pgfutil@tempdimb
% For handling line thickness if you wish "edge touch" and not "overlap"
%\advance\pgf@yb by -.5\pgflinewidth
% Move east triangle corner to east circle center
\advance\pgf@xc by-1.5\pgfutil@tempdima
\advance\pgf@yc by -\pgfutil@tempdimb
% For handling line thickness if you wish "edge touch" and not "overlap"
%\advance\pgf@yc by -.5\pgflinewidth
% 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! :)
Best Answer
Inspired by Andrew Stacey's pretty drawing, here's a take on two of the pictures you linked to. Once you start with drawing stuff like this, you'll pretty quickly accumulate your own library of elements, and every successive drawing will be easier.