Complex objects in TikZ: pgfkeys scope and best practice

I try to define a reusable object in TikZ and run into some troubles. The following minimal working example shows the problem.

\documentclass{article}

\usepackage{tikz}
%\usepackage{trace-pgfkeys}
\usetikzlibrary{calc}

\makeatletter

\pgfkeys{
/object/.cd,
angle/.initial=0,
}

\def\object[#1](#2,#3)#4;{
%  \pgfkeys{/object/.cd,#1,angle/.get=\@angle}
\pgfkeys{/object/.cd,#1}
\node (center) at (#2,#3) {#4};
\coordinate (#4 center) at (center);
%  \draw [rotate around={\@angle:(center)}] ($(center)+(-1,-.5)$) coordinate (#4 a) rectangle ++(2,1);
\draw [rotate around={\pgfkeysvalueof{/object/angle}:(center)}] ($(center)+(-1,-.5)$) coordinate (#4 a) rectangle ++(2,1);
%  \def\@angle{0}
}

\makeatother

\begin{document}
\begin{tikzpicture}
\object[](0,0){A};
\object[angle=30](3,0){B};
\object[](6,0){C};

\draw (A a) -- (B center);
\end{tikzpicture}
\end{document}


The mwe results in the following picture.

Object C is rotated as well, despite no angle is given. That tells me that I misunderstood how pgfkeys works and should be utilized. Desired is that the default value is used if the option angle is not given. How can it be done correct? And how can I get rid of the intermediate variable \@angle?

Bonus question: The manual states, that “.” shall not be used in coordinate names and it also does not work. But “object” coordinates defined by TikZ libraries make heavy use of “x.y” coordinate names. Is there a method to do it in the example above, since it would reflect how it is done by TikZ itself?

Related: this question.

Edit: I replaced some lines in the code so that the helper macro \@angle is no longer needed, see Answer of Qrrbrbirlbel.

-
Could you point us (or just me?) to “object” coordinates? Do you mean something like node.south for example? This is exactly the reason why you must not use . in coordinate/node names. Because the . is used to distinguish between the coordinate/node name (here: node) and the anchor (here: south). –  Qrrbrbirlbel Jan 25 '13 at 17:27
You might not want to denote your nodes with keywords such as center or A center. It would just make your life more difficult. –  percusse Jan 25 '13 at 21:53
@Qrrbrbirlbel: That is exactly what I am searching for in the “bonus question”. I want to define anchors. My object is something like a circuit symbol and has inputs and outputs and I want to define special anchors like (A.input a) and (B.output). I'll go through your long answer tomorrow since it's late here. –  Chris Jan 25 '13 at 22:15
@percusse: that was just meant as an example, see my other comment for explanation. –  Chris Jan 25 '13 at 22:17
@Chris Huh. Have you taken a look at the circuits libraries (ch. 29, pp. 290ff.) or the circuitikz package that served as the inspiration for the TikZ libraries? My guess: You won’t need to create your own shapes and anchors. On that note: Defining new (real) anchors needs either an already defined shape where you just add anchors or a new shape (that can be defined from scratch or inherits some of its defintion from other shapes). Either way: What’s your real use-case scenario? –  Qrrbrbirlbel Jan 25 '13 at 23:10

Notes

• I have removed the calc library and used the keys xshift and yshift directly on the center node.
• The ; in your defintion of \object is actually not needed as you group the last argument anyway. With your definition, even \object[](,)text; is possible. Without the ; you must write \object[](,){text} (though an additional ; wouldn’t hurt than either).

However, I have not changed your argument definition.

• I have provided an improved version of your macro:

• I have replaced your \definition with a \newcommand that allows the first parameter to be optional.
• The algorithm to create the rectangle is—with the help of another node—re-written.

The \pgfkeys in your macro sets the /object/angle key globally. Unless you make the change local (by including your macro in a set of braces { }) or backup its value it will not be changed.

The line

  \def\@angle{0}


will only change the \@angle macro, not the key!

There are other ways to “get” one value of PGF key. One would be the use of \pgfkeysgetvalue or \pgfkeysvalueof:

1. \pgfkeysgetvalue{<key>}{<macro>} saves the value of <key> in <macro>:

\pgfkeys{/object/.cd,#1}
\pgfkeysgetvalue{/object/angle}\@angle

2. \pgfkeysvalueof{<key>} expands to the current value of <key>:

\pgfkeys{/object/.cd,#1} % and three lines later:
\draw [rotate around={\pgfkeysvalueof{/object/angle}:(center)}] ([xshift=-1cm,yshift=-.5cm]center) coordinate (#4 a) rectangle ++(2,1);


You could also save the value directly to a macro:

1. Handler /.store in or /.estore in (expand its argument before saving):

\pgfkeys{
/object/.cd,
angle/.store in=\@angle,
angle=0,
}


Then \@angle can be used without the need to extra “get” the value inside the macro.

2. The handler .store in is just a short-cut, internally \def\@angle{#1} is used. In fact, we can use .code to even calculate the argument. In this case, that does not make much sense because we use the angle only with rotate or rotate around. These keys are already usable with a calculation (e.g. rotate=360-25+2*10).

\pgfkeys{
/object/.cd,
angle/.code=\pgfmathsetmacro\@angle{#1},
angle=0,
}


Code

\documentclass[tikz]{standalone}
\makeatletter
\pgfkeys{
/object/.cd,
angle/.initial=0,
}

\def\object[#1](#2,#3)#4;{{
\pgfkeys{/object/.cd,#1,angle/.get=\@angle}
\node (center) at (#2,#3) {#4};
\coordinate (#4 center) at (center);
\draw [rotate around={\@angle:(center)}] ([xshift=-1cm,yshift=-.5cm]center) coordinate (#4 a) rectangle ++(2,1);
}}
\makeatother

\begin{document}
\begin{tikzpicture}
\object[](0,0){A};
\object[angle=30](3,0){B};
\object[](6,0){C};

\draw (A a) -- (B center);
\end{tikzpicture}
\end{document}


Improvement (?)

Nodes do already understand the concept of rotation but not only is the shape rotated the content is too.

My solution provides two nodes that will be created:

• the un-rotated one with the text (in the example below in lightgray), name: x-<name>; and
• the rotated rectangle with text hidden, name: <name>.

Where <name> is either the name that is provided by the /object/name key or if that is empty the the node’s content (#4) is used.

The rotated node contains the text in \phantom so that it gets resized the same as the un-rotated node in case that the text is bigger than the minimum dimensions.

If you only want an \object to be rectangular, add shape=rectangle to the \node commands. Otherwise it could happen that a shape specification that is given to the command’s scope is used.

Code

\documentclass[tikz]{standalone}
\makeatletter
\pgfkeys{
/object/.cd,
angle/.initial=0,
name/.initial={},
}

\newcommand*{\object}[1][]{\@object[#1]}
\def\@object[#1](#2,#3)#4;{{
\pgfkeys{/object/.cd,#1}
\pgfkeysgetvalue{/object/name}\@name% saves /object/name in \@name
\ifx\@name\pgfutil@empty\edef\@name{#4}\fi% if no name is given use #4
\node[
draw=lightgray,% debug
minimum width=2cm, minimum height=1cm, name/.expand once=x-\@name] at (#2,#3) {#4};
\node[draw,minimum width=2cm, minimum height=1cm, name/.expand once=\@name, rotate=\pgfkeysvalueof{/object/angle}] at (#2,#3) {\phantom{#4}};
}}
\makeatother

\begin{document}
\begin{tikzpicture}
\object(0,0){A};
\object[angle=30](3,0){B};
\object[name=C has another name](6,0){C};

\draw (A.south west) -- (B.center);
\draw[red] (B.north east) -- (x-B.north west) -- (C has another name.mid west);

\draw[blue,bend right,->] (A) edge (B)
(B) edge (C has another name);
\end{tikzpicture}
\end{document}


Output

-
First: thanks for your elaborate answer. I have not been aware of \pgfkeysvalueof, that helps to get rid of the helper macro. My original object already uses optional arguments and I introduced the ; to stay close to the TikZ syntax. The Idea with the optional name is great, I'll use it in my code. I still have the scoping problem, /object/angle seems to be globally overwritten if I change it. It does not happen in your code, but I don't understand the difference. –  Chris Jan 26 '13 at 16:20
@Chris The important sentence here is “Unless you make the change local (by including your macro in a set of braces { }) …”. If you carefully examine the macro in my answer, you will notice that it is built in the form of \def\object[#1](#2,#3)#4;{{ … }}. –  Qrrbrbirlbel Jan 26 '13 at 16:35
Ah, I totally overlooked that part. That works in my original code as well, thank you very much. So in the end it was just a simple TeX scoping problem, nothing special about pgf. –  Chris Jan 28 '13 at 8:15
(was not able to edit my comment, so I continue here ...) I still try to find a solution for “pseudo-anchors” because I would prefer the syntax (object.connection) over (object connection). Do you think it's worth to ask a new question? Maybe one can define an invisible shape within the object to define the anchors? I'll still mark the answer as accepted, since only the bonus question is left. Thanks for your help! –  Chris Jan 28 '13 at 8:22
@Chris Yes, do ask another question. Your fake anchors are a totally different subject. You also may add a better example of your object that comes closer to the complexity of your actual object. –  Qrrbrbirlbel Jan 28 '13 at 14:03

Here is a completely different approach using trapezium shape and shape border rotate key:

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{shapes.geometric}

\tikzset{
rotated rectangle/.style={
shape=trapezium,trapezium angle=90,
shape border uses incircle,shape border rotate=#1,
minimum width=2cm,minimum height=1cm,trapezium stretches=true,
},
}

\begin{document}
\begin{tikzpicture}
\node[draw,rotated rectangle=0] (A) at (0,0) {A};
\node[draw,rotated rectangle=30] (B) at (3,0) {B};
\node[draw,rotated rectangle=0] (C) at (6,0) {C};

\draw (A.bottom left corner) -- (B.center);
\draw[red] (B.top right corner) --  (C.mid west);
\end{tikzpicture}
\end{document}

-
First, thanks for your answer. I see that my question was somehow misleading. My original structure is not a rectangle, it's a complex object (140 lines of TikZ code). And I'd like to understand how to utilize pgfkeys to control the appearance of my object or parts of it. –  Chris Jan 26 '13 at 15:48
@Chris Your question was perfectly clear. My response attempts to answer one aspect: how to rotate the shape without rotate the content. To define complex nodes, you must use pgf directly... –  Paul Gaborit Jan 27 '13 at 9:53
That's what I meant ;-) The problem was about scopes of keys, the rotation itself works fine. Or did I miss something? –  Chris Jan 28 '13 at 8:25
@Chris In your MWE, the shape and the letter are two different objects. In my example, The letter and its shape border are in the same node. –  Paul Gaborit Jan 28 '13 at 15:44
I still don't get it. My real object is approx. 200 lines of TikZ code at the moment, consisting of many nodes and other objects. The rectangle was just meant as an example. I don't want to get rid of the nodes and other “sub objects” within my “meta object”. The letter is there just to indicate what's the name of the meta object, it's not there in my real object (but several other text nodes). –  Chris Jan 28 '13 at 15:59