1

我的任务是为生成图形的 STL 模型的图形绘制程序编写基于 python 的插件。图是由顶点和边组成的对象,其中顶点由 3D 球(镶嵌二十面体)表示,边由两端连接两个球的圆柱体表示。3D 模型的最终结果是将其转储到 STL 文件中以进行 3D 打印。我能够毫无问题地生成球和圆柱体的 3D 模型,但是在生成整体模型以及让球和圆柱体正确连接时遇到了一些问题。

我最初的想法是在原点创建镶嵌二十面体,然后将它们转换为顶点的位置。这工作正常。然后,对于每条边,我会在原点创建一个圆柱体,将其旋转到正确的角度,使其指向正确的方向,然后将其平移到两个顶点之间的中点,以便嵌入圆柱体的末端在二十面体中。这就是事情出错的地方。我在正确旋转时遇到了一些困难。为了计算旋转,我正在执行以下操作:

首先,我找到两点之间的角度如下(其中源和目标都是图中的顶点,属于我当前正在处理的边):

        deltaX = source.x - target.x
        deltaY = source.y - target.y
        deltaZ = source.z - target.z

        xyAngle = math.atan2(deltaX, deltaY) 
        xzAngle = math.atan2(deltaX, deltaZ) 
        yzAngle = math.atan2(deltaY, deltaZ)

计算的角度看起来很合理,据我所知,它实际上代表了顶点之间的角度。例如,如果我在 (1, 1, 0) 处有一个顶点,在 (3, 3, 0) 处有另一个顶点,则连接它们的角边确实显示为两个顶点之间的 45 度角。(那个,或 -135 度,取决于哪个顶点是源,哪个是目标)。

计算完角度后,我创建一个圆柱体并按已计算的角度旋转它,就像这样,使用我创建的其他一些类: c = cylinder() c.createCylinder(edgeThickness, edgeLength)

        c.rotateX(-yzAngle)
        c.rotateY(xzAngle)
        c.rotateZ(-xyAngle)
        c.translate(edgePosition.x, edgePosition.y, edgePosition.z)

(其中 edgePosition 是图中两个顶点之间的中点,edgeThickness 是正在创建的圆柱体的半径,edgeLength 是两个顶点之间的距离)。

如前所述,气缸的旋转没有按预期工作。它似乎在 x/y 平面上进行了正确的旋转,但是一旦边的顶点在所有三个分量(x、y 和 z)中都不同,旋转就会失败。这是一个 x 和 y 分量不同但 z 分量不同的图形示例:

应用程序中的图表

这是生成的 STL 文件,如 Makerware 中所示(用于将 3D 模型发送到 3D 打印机):

Makerware 中的图表

(左下角的额外圆柱体是我目前为了测试目的而留下的——一个指向 z 轴方向的圆柱体,位于原点)。

如果我采用同一张图并将中间顶点在 z 轴上移出,那么现在所有边都涉及所有三个轴上的角度,我得到如下结果:

如应用程序所示:

在此处输入图像描述

生成的 STL 文件,如 Makerware 中所示:

在此处输入图像描述

...以及从侧面看的相同模型:

在此处输入图像描述

正如你所看到的,圆柱体肯定没有像我想象的那样与球相遇。我的问题是:我做这件事的方法是有缺陷的,还是我在轮换的某个地方犯了一些小但关键的错误?我很确定旋转功能本身没有问题,因为我已经能够独立验证它们是否按预期工作。我还尝试创建一个旋转函数,该函数接受偏航、俯仰和滚动并同时执行所有三项,它似乎生成了相同的结果,如下所示:

c.rotateYawPitchRoll(xzAngle, -yzAngle, -xyAngle)

所以......有人对我可能做错了什么有任何想法吗?

更新:正如 joojaa 指出的那样,它是计算正确角度以及应用它们的顺序的组合。为了让事情正常进行,我首先计算 x 轴上的旋转,如下所示:

zyAngle = math.atan2(deltaVector.z, deltaVector.y)

其中 deltaVector 是目标向量和源向量之间的差异。但是,此轮换尚未应用!下一步是计算y轴上的旋转,如下:

angle = vector.angleBetweenVectors(vector(target.x - source.x, target.y - source.y, target.z - source.z), vector(target.x - source.x, target.y - source.y, 0.0))

一旦计算了两个旋转,它们就会被应用......以相反的顺序!首先是 x,然后是 y:

c.rotateY(angle)
c.rotateX(-zyAngle) #... where c is a cylinder object

似乎仍然存在一些错误,但这似乎至少适用于一个简单的测试用例。

4

1 回答 1

1

旋转以连续的顺序发生,因此角度相互影响。不可能使用欧拉模型一次旋转它们。这就是为什么您不能只根据第一个静态情况计算旋转。想象一下转动一个立方体,使它直立在角落里。是的,第一次旋转是 45,但第二次不是,因为此时立方体已经转动(画出序列的每一步,看看会发生什么)。空间旋转并非微不足道。

所以你需要旋转一个角度然后重新计算第二个角度等等。这也是为什么您的第一次轮换工作正常的原因。除非您有兴趣确保围绕轴的旋转具有特定方向,否则您只需要 2 次旋转。

我建议您使用轴角或矩阵来代替。主要是因为在轴角度中这是微不足道的,角度是沿管开始和结束向量之间的点,轴是这两个向量之间的交叉点。然后,如果需要,您可以将它们转换为欧拉角。但也许你可以直接使用矩阵。有关如何转换以及如何直接计算旋转的想法,请参见: Christoph Gohlke 的transformations.py。另请参阅随附的 c 源代码。

我想我需要稍微扩展一下这个答案

这个问题有一个非常简单的出路,可以回避您和许多其他人的所有问题。答案是不要使用欧拉角旋转。我花了很多脑力试图将欧拉旋转解释为最终在没有欧拉旋转的情况下更容易解决的问题。如果你想更多地想出更多的答案,我会留下一个理由来证明这一点。

大多数使用欧拉旋转序列的原因是您可能不了解欧拉角。事实上,只有少数情况是好的。没有自尊的程序员使用欧拉旋转来解决这个问题。您所做的是使用矢量数学代替。

所以你有从源到目标的方向向量,通常计算:

along = normalize(target-source)

这只是您的矩阵行之一(或列符号取决于模型制造商),与您的圆柱体原始方向相对应的行(行只是 xyzw),然后您需要另一个垂直于该向量的向量。选择一个任意向量,如向上(如果您的沿指向靠近向上,则选择向左)。将这个向上向量与第二行方向的沿相乘。最后将您的来源作为最后一行,最后一列中为 1。完成完全形成的仿射矩阵描述圆柱优先级。由于您可以绘制矢量,因此更容易理解。

有更短的方法,但这个很容易理解。

于 2013-06-07T05:42:39.800 回答