最近想用TikZ画一下化学中的一些器材,通过参考latex-glasswaretikz-labo实现了试管、烧瓶等的绘制。

现有一个疑惑,当给scope传入rotate旋转参数后,也会造成液面的统一旋转,从而无法保持液面的水平状态。

一个简单的想法是在scope内能取得传入rotate旋转角度,然后对溶液和液面椭圆进行反向旋转。

为验证想法,用45度进行了测试,反旋转可以实现液面保持水平。

但是,没有找到如何在scope内获取rotate旋转角度的方法,各位网友可否指点一二?

目前的绘制结果为:

main-new-1.png

实现的MWE为:


\documentclass[margin=3pt,
  convert,
  convert={
    outext=.png,
    command=\unexpanded{
      pdftocairo -r 600 -png \infile % 将生成的pdf文件转换为png图像
    }
  }
  ]{standalone}
\usepackage{ctex}
\usepackage{ifthen}
\newcommand{\ifnonzero}[2]{
 \ifthenelse{\equal{#1}{0}}{}{#2}
}
% \usepackage{calc}
\usepackage{tikz}
\usetikzlibrary{calc}
\usetikzlibrary{intersections}
% 烧瓶
% 根据梯形面积等分刻度
% 用定积分法计算等分线:
% 瓶底半径:1
% 瓶口半径:0.25
% 高度:2(不含颈部)
% 顺时针转90度,用两点公式写出直线方程:y=(-3/8)*x + 1
% 求积分:(-3/16)*x*x + x
% 0-2定积分得总面积为:5/4
% 根据传入的#3比例参数,确定当前面积为:#3 * 5 / 4
% 解方程:(-3/16)*x*x + x - #3 * 5 / 4 = 0
% 可得用于绘制的液面高度
\newcommand{\flask}[3][]{
  \begin{scope}[shift={(#2)},glassware,#1]
    % 方程系数
    \pgfmathsetmacro{\a}{-3/16}
    \pgfmathsetmacro{\b}{1}
    \pgfmathsetmacro{\m}{-1 * \b / (2 * \a)}
    % 液面系数为不0
    \ifnonzero{#3}{
      % 烧瓶外框路径,并构成裁剪区域
      \clip[rounded corners, name path = P1] (-0.4, 3)--++(0.15,-0.1)
        --++(0,-0.9)--++(-0.75, -2.0)to[bend right=10pt]++(2.0, 0.0)--++(-0.75, 2.0)
        --++(0.0, 0.9)--++(0.15, 0.10)
        ++(-0.4, 0.0) circle [x radius=0.4, y radius=0.065]--cycle;%  
      % 计算液面高度
      \pgfmathsetmacro{\c}{-1 * #3 * 5 / 4}
      \pgfmathsetmacro{\delta}{\b * \b - 4 * \a * \c}
      \pgfmathsetmacro{\n}{sqrt(abs(\delta)) / (2 * abs(\a))}
      \pgfmathsetmacro{\i}{\m - \n}
      % 绘制液面矩形并被烧瓶裁剪
      % TODO:如何获得给scope转入的rotate旋转参数
      %       从而进行反旋转以保持液面水平?
      \fill[name path=P2] (-1.0, -3.5pt) rectangle (1.0, \i);
      % 绘制液面椭圆标志
      % 求交点
      \path [name intersections={of=P1 and P2, by={E,F}}];
      % 计算中点
      \node (M) at ($(E)!0.5!(F)$){};
      % 计算半径并绘制液面椭圆标志
      \fill[draw=white, very thin] (E) let \p1 = ($(M)-(E)$),
                                           \n2 = {veclen(\x1,\y1)}
        in ++(\n2, 0) circle[x radius = \n2, y radius = 2.5pt];
    }
    % 绘制刻度线(用积分法计算出刻度位置)
    \pgfmathtruncatemacro{\lticklable}{1}
    % 计算刻度位置
    \pgfmathsetmacro{\c}{-1 * 5 / 24}
    \pgfmathsetmacro{\delta}{\b * \b - 4 * \a * \c}
    \pgfmathsetmacro{\n}{sqrt(abs(\delta)) / (2 * abs(\a))}
    \pgfmathsetmacro{\i}{\m - \n}
    % 多带带处理第1个坐标刻度,以便在循环中可以处理大刻度(mod 5运算)
    \draw (-0.1, \i)--(0.1, \i) node[right, xshift=-3pt](\lticklable){\tiny{}\lticklable};      
    \foreach \y[count=\x] in {6,7,...,25}% 刻度位置占有单位面积数
    {
      \pgfmathsetmacro{\c}{-1 * \y / 24}
      \pgfmathsetmacro{\delta}{\b * \b - 4 * \a * \c}
      \pgfmathsetmacro{\n}{sqrt(abs(\delta)) / (2 * abs(\a))}
      \pgfmathsetmacro{\i}{\m - \n}    
      \pgfmathtruncatemacro{\ltick}{mod(\x, 5)}
      \ifnum\ltick=0
        \pgfmathtruncatemacro{\lticklable}{\x / 5 + 1}
        \draw (-0.1,\i)--(0.1,\i) node[right, xshift=-3pt](\lticklable){\tiny{}\lticklable};
      \else
        \draw (-0.05,\i)--(0.05,\i);
      \fi
    }
    % 绘制烧瓶外框
    \draw[rounded corners] (-0.4, 3)--++(0.15,-0.1)
      --++(0,-0.9)--++(-0.75, -2.0)to[bend right=10pt]++(2.0, 0.0)--++(-0.75, 2.0)
      --++(0.0, 0.9)--++(0.15, 0.10)
      ++(-0.4, 0.0) circle [x radius=0.4, y radius=0.065]--cycle;%  
  \end{scope}
}
\newcommand{\iflask}[3][]{
  \begin{scope}[shift={(#2)},glassware,#1]
    % 方程系数
    \pgfmathsetmacro{\a}{-3/16}
    \pgfmathsetmacro{\b}{1}
    \pgfmathsetmacro{\m}{-1 * \b / (2 * \a)}
    % 液面系数为不0
    \ifnonzero{#3}{
      % 烧瓶外框路径,并构成裁剪区域
      \clip[rounded corners, name path = P1] (-0.4, 3)--++(0.15,-0.1)
        --++(0,-0.9)--++(-0.75, -2.0)to[bend right=10pt]++(2.0, 0.0)--++(-0.75, 2.0)
        --++(0.0, 0.9)--++(0.15, 0.10)
        ++(-0.4, 0.0) circle [x radius=0.4, y radius=0.065]--cycle;%  
      % 计算液面高度
      \pgfmathsetmacro{\c}{-1 * #3 * 5 / 4}
      \pgfmathsetmacro{\delta}{\b * \b - 4 * \a * \c}
      \pgfmathsetmacro{\n}{sqrt(abs(\delta)) / (2 * abs(\a))}
      \pgfmathsetmacro{\i}{\m - \n}
      % 绘制液面矩形并被烧瓶裁剪
      % 旋转
      \fill[rotate=45,name path=P2] (-3.0, -25pt) rectangle (3.0, \i);
      % 绘制液面椭圆标志
      % 反向旋转45度
      \path [name intersections={of=P1 and P2, by={E,F}}];
      % 计算中点
      \node (M) at ($(E)!0.5!(F)$){};
      % 计算半径并绘制液面椭圆标志
      % 反向旋转45度
      \fill[rotate = 45, draw=white, very thin] (E) let \p1 = ($(M)-(E)$),
                                           \n2 = {veclen(\x1,\y1)}
        in ++(\n2, 0) circle[x radius = \n2, y radius = 2.5pt];
    }
    % 绘制刻度线(用积分法计算出刻度位置)
    \pgfmathtruncatemacro{\lticklable}{1}
    % 计算刻度位置
    \pgfmathsetmacro{\c}{-1 * 5 / 24}
    \pgfmathsetmacro{\delta}{\b * \b - 4 * \a * \c}
    \pgfmathsetmacro{\n}{sqrt(abs(\delta)) / (2 * abs(\a))}
    \pgfmathsetmacro{\i}{\m - \n}
    % 多带带处理第1个坐标刻度,以便在循环中可以处理大刻度(mod 5运算)
    \draw (-0.1, \i)--(0.1, \i) node[right, xshift=-3pt](\lticklable){\tiny{}\lticklable};      
    \foreach \y[count=\x] in {6,7,...,25}% 刻度位置占有单位面积数
    {
      \pgfmathsetmacro{\c}{-1 * \y / 24}
      \pgfmathsetmacro{\delta}{\b * \b - 4 * \a * \c}
      \pgfmathsetmacro{\n}{sqrt(abs(\delta)) / (2 * abs(\a))}
      \pgfmathsetmacro{\i}{\m - \n}    
      \pgfmathtruncatemacro{\ltick}{mod(\x, 5)}
      \ifnum\ltick=0
        \pgfmathtruncatemacro{\lticklable}{\x / 5 + 1}
        \draw (-0.1,\i)--(0.1,\i) node[right, xshift=-3pt](\lticklable){\tiny{}\lticklable};
      \else
        \draw (-0.05,\i)--(0.05,\i);
      \fi
    }
    % 绘制烧瓶外框
    \draw[rounded corners] (-0.4, 3)--++(0.15,-0.1)
      --++(0,-0.9)--++(-0.75, -2.0)to[bend right=10pt]++(2.0, 0.0)--++(-0.75, 2.0)
      --++(0.0, 0.9)--++(0.15, 0.10)
      ++(-0.4, 0.0) circle [x radius=0.4, y radius=0.065]--cycle;%  
  \end{scope}
}
\begin{document}
\begin{tikzpicture}
  \tikzstyle{glassware} = [fill=magenta!15]
  \flask{0,0}{0.50}
  \flask[rotate=45]{4,0.5}{0.80}
  \iflask[rotate=-45]{6,0.5}{0.60}
\end{tikzpicture}
\end{document}


2 回答2

1
<p><br></p><p>可以使用选项<span style="color: rgb(192, 0, 0);"> /tikz/reset cm</span> 把变换矩阵还原为单位矩阵。</p><p>\newcommand{\flask}[3][]{<br>  \begin{scope}[<span style="color: rgb(192, 0, 0);">reset cm</span>,#2,fill=magenta!15,#1]<br>%..........................................................省略<br>      % TODO:如何获得给scope转入的rotate旋转参数<br>      %       从而进行反旋转以保持液面水平?<br>      <span style="color: rgb(192, 0, 0);">\begin{scope}[reset cm]</span><br>      \fill[name path=P2] (current bounding box.south west) rectangle (current bounding box.south east |- 0, \i);<br>      % 绘制液面椭圆标志<br>      % 求交点<br>      \path [name intersections={of=P1 and P2, by={E,F}}];<br>      % 计算中点<br>      \node (M) at ($(E)!0.5!(F)$){};<br>      % 计算半径并绘制液面椭圆标志<br>      \fill[draw=white, very thin] (E) let \p1 = ($(M)-(E)$),<br>                                           \n2 = {veclen(\x1,\y1)}<br>        in ++(\n2, 0) circle[x radius = \n2, y radius = 2.5pt];<br>     <span style="color: rgb(192, 0, 0);"> \end{scope}</span><br>%.................................................................省略<br>  \end{scope}<br>}<br></p><p><br></p><p>然后效果是:</p><p><br></p><p>\begin{tikzpicture}<br>  \flask{rotate=69}{0.50}<br>\end{tikzpicture}</p><p><br></p><p><img src="/data/ueditor/php/upload/image/20200430/1588259509912973.png" title="1588259509912973.png" alt="2020-04-30 23-11-26屏幕截图.png" style="max-width:650px"></p><p>图形中这个倾斜角度有一点问题(杯口那里),要避免这个问题,可能需要一些复杂的计算。</p><p><br></p><p>我对<span style="color: rgb(0, 0, 0);">TikZ变换的了解是:</span></p><p>1.如果想获得当前的变换矩阵,可以使用命令 <span style="color: rgb(192, 0, 0);">\pgfgettransform<span style="color: rgb(0, 0, 0);">,变换矩阵的主要元素是 \pgf@pt@aa, \pgf@pt@ab, \pgf@pt@ba, \pgf@pt@ba 这4个宏(数值),和 \pgf@pt@x, \pgf@pt@y 这2个尺寸寄存器。<br></span></span></p><p><span style="color: rgb(0, 0, 0);">2.</span>直接改变变换矩阵的是命令 <span style="color: rgb(192, 0, 0);">\pgftransformcm<span style="color: rgb(0, 0, 0);">。</span></span></p><p><span style="color: rgb(0, 0, 0);">3.顶层的 TikZ 命令、变换选项不会直接把变换矩阵应用于点的坐标,对点坐标应用变换矩阵的是底层的 PGF 命令,例如</span><span style="color: rgb(192, 0, 0);">\pgfpathmoveto<span style="color: rgb(0, 0, 0);">, 它会调用命令</span> \pgfpointtransformed <span style="color: rgb(0, 0, 0);">做变换,变换后的坐标构成软路径。</span><br></span></p><p><span style="color: rgb(0, 0, 0);">4.TikZ的变换选项基本上都调用内部命令 \tikz@addtransform 来重定义宏 \tikz@transform, 宏<span style="color: rgb(0, 0, 0);"> \tikz@transform</span> 保存一系列的底层变换命令。当宏<span style="color: rgb(0, 0, 0);"> \tikz@transform</span> 被执行时,其中的各个变换命令依次起作用,也就是依次修改变换矩阵。<br></span></p><p><span style="color: rgb(0, 0, 0);">5.如果只用<span style="color: rgb(0, 0, 0);">TikZ</span>的命令来画“烧杯”,那么<span style="color: rgb(0, 0, 0);"><span style="color: rgb(0, 0, 0);">TikZ</span>的</span>变换选项差不多就够用了,不过有时候使用底层的命令会更方便。</span></p><p><span style="color: rgb(192, 0, 0);">我的理解可能不对,仅供参考。</span><br></p>
  • 非常感谢!还是texdoc不够仔细,该罚! – registor 2020-05-01 06:57 回复
2
<p style="white-space: normal;">另外定义一个pgfkey,在这个key的code里设置rotate并将值存储到一个宏里<br></p><pre class="brush:plain;toolbar:false">\documentclass{article} \usepackage{tikz} \pgfkeys{   /rotate/.code={     \pgfkeysalso{/tikz/rotate=#1}     \def\rot{#1}   } } \begin{document} \begin{tikzpicture} \begin{scope}[/rotate=45]   \draw (0, 0) -- node[right] {\rot} (1, 0); \end{scope} \end{tikzpicture} \end{document}</pre><p style="white-space: normal;"><img src="https://wenda.latexstudio.net/data/ueditor/php/upload/image/20200430/1588256819332077.png" title="1588256819332077.png" alt="Screenshot from 2020-04-30 22-26-30.png" style="max-width: 650px;"></p><p><br></p>
  • 简洁明快的办法,谢谢! – registor 2020-05-01 06:58 回复

你的回答

请登录后回答

你的回答将会帮助更多人,请务必认真回答问题。