如何在scope环境内获得rotate旋转参数?

2020-04-30 15:07发布

最近想用TikZ画一下化学中的一些器材,通过参考latex-glassware和tikz-labo实现了试管、烧瓶等的绘制。现有一个疑惑,当给scope传入rotate旋转参数后,也会造成液面的统一旋...

最近想用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条回答
咸菜的味道
2020-05-01 00:08 .采纳回答


可以使用选项 /tikz/reset cm 把变换矩阵还原为单位矩阵。

\newcommand{\flask}[3][]{
  \begin{scope}[reset cm,#2,fill=magenta!15,#1]
%..........................................................省略
      % TODO:如何获得给scope转入的rotate旋转参数
      %       从而进行反旋转以保持液面水平?
      \begin{scope}[reset cm]
      \fill[name path=P2] (current bounding box.south west) rectangle (current bounding box.south east |- 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];
      \end{scope}
%.................................................................省略
  \end{scope}
}


然后效果是:


\begin{tikzpicture}
  \flask{rotate=69}{0.50}
\end{tikzpicture}


2020-04-30 23-11-26屏幕截图.png

图形中这个倾斜角度有一点问题(杯口那里),要避免这个问题,可能需要一些复杂的计算。


我对TikZ变换的了解是:

1.如果想获得当前的变换矩阵,可以使用命令 \pgfgettransform,变换矩阵的主要元素是 \pgf@pt@aa, \pgf@pt@ab, \pgf@pt@ba, \pgf@pt@ba 这4个宏(数值),和 \pgf@pt@x, \pgf@pt@y 这2个尺寸寄存器。

2.直接改变变换矩阵的是命令 \pgftransformcm

3.顶层的 TikZ 命令、变换选项不会直接把变换矩阵应用于点的坐标,对点坐标应用变换矩阵的是底层的 PGF 命令,例如\pgfpathmoveto, 它会调用命令 \pgfpointtransformed 做变换,变换后的坐标构成软路径。

4.TikZ的变换选项基本上都调用内部命令 \tikz@addtransform 来重定义宏 \tikz@transform, 宏 \tikz@transform 保存一系列的底层变换命令。当宏 \tikz@transform 被执行时,其中的各个变换命令依次起作用,也就是依次修改变换矩阵。

5.如果只用TikZ的命令来画“烧杯”,那么TikZ变换选项差不多就够用了,不过有时候使用底层的命令会更方便。

我的理解可能不对,仅供参考。

一周热门 更多>