{"id":19974348,"url":"https://github.com/gbroques/freecad-rotations","last_synced_at":"2026-03-19T12:52:54.151Z","repository":{"id":94020678,"uuid":"394052303","full_name":"gbroques/freecad-rotations","owner":"gbroques","description":"A guide on understanding how rotations work in FreeCAD.","archived":false,"fork":false,"pushed_at":"2021-08-20T01:19:54.000Z","size":431,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-01T18:44:45.139Z","etag":null,"topics":["euler-angles","freecad","rotation","rotation-matrices","rotation-matrix","rotations","rotations-operations","tait-bryan-angles","tutorial"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gbroques.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-08-08T19:24:56.000Z","updated_at":"2024-09-16T06:19:40.000Z","dependencies_parsed_at":"2023-03-12T13:00:21.639Z","dependency_job_id":null,"html_url":"https://github.com/gbroques/freecad-rotations","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/gbroques/freecad-rotations","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gbroques%2Ffreecad-rotations","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gbroques%2Ffreecad-rotations/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gbroques%2Ffreecad-rotations/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gbroques%2Ffreecad-rotations/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gbroques","download_url":"https://codeload.github.com/gbroques/freecad-rotations/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gbroques%2Ffreecad-rotations/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268531912,"owners_count":24265250,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-03T02:00:12.545Z","response_time":2577,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["euler-angles","freecad","rotation","rotation-matrices","rotation-matrix","rotations","rotations-operations","tait-bryan-angles","tutorial"],"created_at":"2024-11-13T03:14:44.716Z","updated_at":"2026-02-09T11:31:10.698Z","avatar_url":"https://github.com/gbroques.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# FreeCAD Rotations\n\nA guide on understanding how rotations work in FreeCAD.\n\nUnderstanding a rotation about one axis is simple to understand.\n\nThis guide focuses on rotating about more than one axis.\n\n## Prerequisites\n\nInstall [FreeCAD 19.2].\n\n## Rotating About More Than One Axis\n\n### Cone Instructions\n\n1. Start FreeCAD.\n2. Select **Part** workbench from workbench dropdown.\n\n   [![Part workbench](./part-workbench.png)](https://wiki.freecadweb.org/Part_Module)\n\n3. Click \"*Create a cone solid*\" button on toolbar.\n\n   [![Create a cone solid](./Part_Cone.svg)](https://wiki.freecadweb.org/Part_Cone)\n\n4. Set the `Radius1` property of the **Cone** to `0.00 mm`.\n5. Select **View** \u003e **Toggle axis cross** (`A`, `C`).\n   * Red, Green, and Blue represents X, Y, and Z axes respectively.\n\n   ![Cone before rotations](./cone-before-rotations.png)\n\n7. With Cone selected, select **Edit** \u003e **Placement** from the top main file menu.\n9. Select \"*Euler Angles (xy'z\")*\" from the dropdown under \"*Rotation*\".\n   * Positive rotations are clockwise when viewed from the Origin along an axis — or counter-clockwise when viewed towards the Origin (see [Right-hand rule](https://en.wikipedia.org/wiki/Right-hand_rule)).\n\n   ![Right-hand Rule](./right-hand-rule.svg)\n\n   * Also see, [explanation of xy'z\" notation](https://en.wikipedia.org/wiki/Euler_angles#Conventions).\n   * x-y'-z\" (intrinsic rotations) or z-y-x (extrinsic rotations).\n8. Enter 90° around x-axis.\n\n   ![Cone rotated around x-axis by 90 degrees](./cone-rotated-around-x-axis-by-90-degrees.png)\n\n9. and 90° around z-axis.\n\n   ![Cone rotated around x and z axes by 90 degrees](./cone-rotated-around-x-and-z-axes-by-90-degrees.png)\n\n10. Click the **OK** button.\n11. The `Angle` is **120°**, and `Axis` is (**0.58**, **0.58**, **0.58**).\n   * But *how is this calcuated*?\n\n### Euler Angles\n\n[Euler angles] combine a series of rotations around X, Y, and Z axes into a *single* rotation about *one* axis.\n\nA rotation about Z, Y, and X axes is also know as a rotation about **Y**aw, **P**itch, and **R**oll axes ([from aircraft axes]).\n\n|Yaw (Z)|\n|:-----:|\n|![Yaw](./yaw.gif)|\n\n|Pitch (Y)|\n|:-------:|\n|![Pitch](./pitch.gif)|\n\n|Roll (X)|\n|:------:|\n|![Roll](./roll.gif)|\n\n\u003e ***GIF Source:** [FreeCAD Wiki: Position and Yaw, Pitch and Roll].*\n\nThe order of multiplication is Yaw, Pitch, Roll.[¹][1]\n\nEnsure **View** \u003e **Panels** \u003e **Python console** is checked.\n\nWe can calculate the `Angle` and `Axis` vector and using FreeCAD.\n\n```python\n\u003e\u003e\u003e from FreeCAD import Rotation, Vector\n\u003e\u003e\u003e yaw = Rotation(Vector(0, 0, 1), 90)\n\u003e\u003e\u003e roll = Rotation(Vector(1, 0, 0), 90)\n\u003e\u003e\u003e rotation = yaw.multiply(roll)\n\u003e\u003e\u003e rotation.Axis\nVector (0.5773502691896258, 0.5773502691896256, 0.5773502691896258)\n\u003e\u003e\u003e from math import degrees\n\u003e\u003e\u003e degrees(rotation.Angle)\n119.99999999999999\n```\n\nHow does FreeCAD caculate this though?\n\n### [Euler Angles] to [Axis–angle representation]\n\n\u003e The following formula doesn't work if the rotation matrix is symmetric! For example (-90, 0, 180) in (yaw, pitch, roll) or (z, y, x).\n\n#### Three Elemental Rotation Matrices\n\n`Yaw(θ)`\n```\n┌ cos(θ)  -sin(θ)  0 ┐\n│ sin(θ)   cos(θ)  0 │\n└ 0        0       1 ┘\n```\n\n`Pitch(θ)`\n```\n┌  cos(θ)  0  sin(θ) ┐\n│  0       1  0      │\n└ -sin(θ)  0  cos(θ) ┘\n```\n\n`Roll(θ)`\n```\n┌ 1  0        0      ┐\n│ 0  cos(θ)  -sin(θ) │\n└ 0  sin(θ)   cos(θ) ┘\n```\n\n---\n\nUsing these elemental rotation matrices, we substitute our angle for θ, for each corresponding axis.\n\n`Yaw(90)`\n```\n┌ cos(90)  -sin(90)  0 ┐\n│ sin(90)   cos(90)  0 │\n└ 0        0         1 ┘\n```\n\n`Roll(90)`\n```\n┌ 1  0         0       ┐\n│ 0  cos(90)  -sin(90) │\n└ 0  sin(90)   cos(90) ┘\n```\n\n---\n\nThen we evaluate `sin(90)` and `cos(90)`, which results in `1` and `0` respectively.\n\n`Yaw(90)`\n```\n    0   -1    0\n    1    0    0\n    0    0    1\n```\n\n`Roll(90)`\n```\n    1    0    0\n    0    0   -1\n    0    1    0\n```\n\n---\n\nFinally, we multiply `Yaw(90)` with `Roll(90)` using [Matrix multiplication] and [WolframAlpha](https://www.wolframalpha.com/input/?i=%7B%7B0%2C+-1%2C+0%7D%2C+%7B1%2C+0%2C+0%7D%2C+%7B0%2C+0%2C+1%7D%7D+%7B%7B1%2C+0%2C+0%7D%2C+%7B0%2C+0%2C+-1%7D%2C+%7B0%2C+1%2C+0%7D%7D) (*individual steps not shown*).\n\n```\n    0    0    1\n    1    0    0\n    0    1    0\n```\n\nWe can verify these with a `print_matrix` function.\n```python\nfrom FreeCAD import Matrix\n\ndef print_matrix(matrix: Matrix, precision=2, width=5) -\u003e None:\n    translation_vector = [matrix.A14, matrix.A24, matrix.A34]\n    has_translation = all(translation_vector)\n    num_dimensions = 4 if has_translation else 3\n    for i in range(1, num_dimensions + 1):\n        for j in range(1, num_dimensions + 1):\n            attr = 'A' + str(i) + str(j)\n            # + 0 to format -0 as positive 0.\n            value = round(getattr(matrix, attr), ndigits=precision) + 0\n            print(\"{:\u003e{width}}\".format(value, width=width), end='')\n        print()\n```\n\n```python\n\u003e\u003e\u003e print_matrix(yaw.toMatrix(), precision=None)\n    0   -1    0\n    1    0    0\n    0    0    1\n\u003e\u003e\u003e print_matrix(roll.toMatrix(), precision=None)\n    1    0    0\n    0    0   -1\n    0    1    0\n\u003e\u003e\u003e print_matrix(rotation.toMatrix(), precision=None)\n    0    0    1\n    1    0    0\n    0    1    0\n```\n\n#### Angle\n\nThe `Angle`, `θ`, can be calcuated by using the following formula.[²][2]\n```\nθ = arccos(tr(R) - 1 / 2)\n```\nWhere `R` is the `rotation` matrix above.\n\n`tr(R)` means calculate the [trace] of `R` which is the sum of the elements on the main diagonal.\n```\ntr(R) = 0 + 0 + 0 = 0\n```\n\nSubstituting `0` for `tr(R)` results in the following simplified formula.\n```\nθ = arccos(-1/2)\n```\n\nWe can then use Python to calculate `theta` for us.\n```python\n\u003e\u003e\u003e from math import degrees, acos\n\u003e\u003e\u003e theta = acos(-1/2)\n\u003e\u003e\u003e degrees(theta)\n120.00000000000001\n```\n\n#### Axis\nWhere `R` is the `rotation` matrix above.\n```\n    ┌ R₁₁  R₁₂  R₁₃ ┐\nR = │ R₂₁  R₂₂  R₂₃ │\n    └ R₃₁  R₃₂  R₃₃ ┘\n```\n\nSubstitute our values in.\n```\n    ┌ 0  0  1 ┐\nR = │ 1  0  0 │\n    └ 0  1  0 ┘\n```\n\nA vector `u` is computed using the following.\n```\n    ┌ R₃₂ - R₂₃ ┐\nu = │ R₁₃ - R₃₁ │\n    └ R₂₁ - R₁₂ ┘\n```\n\nSubstitute our values in\n```\n    ┌ 1 - 0 ┐\nu = │ 1 - 0 │\n    └ 1 - 0 ┘\n```\n\nComplete the calculation.\n```\n    ┌ 1 ┐\nu = │ 1 │\n    └ 1 ┘\n```\n\nWe then normalize the axis vector `u` from above to calculate the `Axis` vector.[²][2]\n```\nw = (1 / 2 * sin(θ)) * u\n```\n\nIn python.\n```python\n\u003e\u003e\u003e from math import sin, acos\n\u003e\u003e\u003e 1 / (2 * sin(theta))\n0.5773502691896258\n\u003e\u003e\u003e u = Vector(1, 1, 1)\n\u003e\u003e\u003e 1 / (2 * sin(theta)) * u\nVector (0.5773502691896258, 0.5773502691896258, 0.5773502691896258)\n```\n\n## General Rotations\n\nThe following matrix product uses the following nomenclature:\n\n* `1`, `2`, `3` subscripts represent the angles `α`, `β` and `γ` (i.e. the angles corresponding to the first, second and third elemental rotations respectively).\n* `X`, `Y`, `Z` are the matrices representing the elemental rotations about the axes x, y, z (e.g. `Z₁` represents a rotation about z by an angle α).\n* `s` and `c` represent **s**ine and **c**osine (e.g. `s₁` represents the sine of α).\n\n\n```\n         ┌ c₁c₂   c₁s₂s₃ - c₃s₁     s₁s₃   + c₁c₃s₂ ┐\nZ₁Y₂X₃ = │ c₂s₁   c₁c₃   + s₁s₂s₃   c₃s₁s₂ - c₁s₃   │\n         └ -s₂    c₂s₃              c₂c₃            ┘\n```\n**Sources:** *source*[¹][1] *source*[³][3]\n\n## [Euler angles] to [Quaternion]\n\n```python\nfrom math import cos, radians, sin\nfrom typing import Tuple\n\n\ndef euler_to_quaternion(yaw: float,\n                        pitch: float,\n                        roll: float) -\u003e Tuple[float, float, float, float]:\n    \"\"\"\n    Convert Euler angles (in degrees) to quaternion form:\n        q0 = x, q1 = y, q2 = z and q3 = w\n    where the quaternion is specified by q = w + xi + yj + zk.\n\n    See:\n        https://github.com/FreeCAD/FreeCAD/blob/0.19.2/src/Base/Rotation.cpp#L632-L658\n        https://en.wikipedia.org/wiki/Quaternion\n    \"\"\"\n    y = radians(yaw)\n    p = radians(pitch)\n    r = radians(roll)\n\n    c1 = cos(y / 2.0)\n    s1 = sin(y / 2.0)\n    c2 = cos(p / 2.0)\n    s2 = sin(p / 2.0)\n    c3 = cos(r / 2.0)\n    s3 = sin(r / 2.0)\n\n    qx = (c1 * c2 * s3) - (s1 * s2 * c3)\n    qy = (c1 * s2 * c3) + (s1 * c2 * s3)\n    qz = (s1 * c2 * c3) - (c1 * s2 * s3)\n    qw = (c1 * c2 * c3) + (s1 * s2 * s3)\n\n    return (qx, qy, qz, qw)\n\n```\n\n```python\n\u003e\u003e\u003e euler_to_quaternion(-90, 0, 180)\n(0.7071067811865476, -0.7071067811865475, -4.329780281177466e-17, 4.329780281177467e-17)\n```\n\n## [Quaternion] to [Axis–angle representation]\n\n```python\nfrom math import acos, degrees, sqrt\nfrom typing import Tuple\n\n\ndef quaternion_to_axis_angle(quaternion: Tuple[float, float, float, float]) -\u003e Tuple[Tuple[float, float, float], float]:\n    \"\"\"\n    Convert quaternion to axis-angle form.\n\n    Axis-angle is a two-element tuple where\n    the first element is the axis vector (x, y, z),\n    and the second element is the angle in degrees.\n\n    See:\n        https://github.com/FreeCAD/FreeCAD/blob/0.19.2/src/Base/Rotation.cpp#L119-L140\n        https://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm\n    \"\"\"\n    qx, qy, qz, qw = quaternion\n\n    s = sqrt(1 - qw**2)\n    normalization_factor = 1 if s \u003c 0.001 else s\n    x = qx / normalization_factor\n    y = qy / normalization_factor\n    z = qz / normalization_factor\n    axis = (x, y, z)\n\n    angle = degrees(2 * acos(qw))\n\n    return (axis, angle)\n\n```\n\n**Source:** https://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm\n\n## References\n\n* [Rotation matrix]\n* [Euler angles]\n* [Axis–angle representation]\n* [Quaternion]\n* [Conversion between quaternions and Euler angles]\n* [Symmetric matrix]\n\n## Additional Resources\n\n* [Rotation and the Right Hand Rule](https://www.youtube.com/watch?v=-EymCI1g5rs)\n* [Why do we use 4x4 Matrices in Computer Graphics?](https://www.youtube.com/watch?v=Do_vEjd6gF0)\n* [Euler Angles - Interactive 3D Graphics](https://www.youtube.com/watch?v=q0jgqeS_ACM)\n* [Singularity in 3D rotation angle sequences](https://robotacademy.net.au/lesson/singularity-in-3d-rotation-angle-sequences/)\n\n\n[Conversion between quaternions and Euler angles]: https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles\n[Quaternion]: https://en.wikipedia.org/wiki/Quaternion\n[Symmetric matrix]: https://en.wikipedia.org/wiki/Symmetric_matrix\n[FreeCAD 19.2]: https://github.com/FreeCAD/FreeCAD/releases/tag/0.19.2\n[Euler angles]: https://en.wikipedia.org/wiki/Euler_angles\n[from aircraft axes]: https://en.wikipedia.org/wiki/Aircraft_principal_axes\n[FreeCAD Wiki: Position and Yaw, Pitch and Roll]: https://wiki.freecadweb.org/Placement#Position_and_Yaw.2C_Pitch_and_Roll\n[1]: https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix\n[Rotation matrix]: https://en.wikipedia.org/wiki/Rotation_matrix\n[trace]: https://en.wikipedia.org/wiki/Trace_(linear_algebra)\n[2]: https://en.wikipedia.org/wiki/Axis%E2%80%93angle_representation#Log_map_from_SO(3)_to_%7F'%22%60UNIQ--postMath-0000000D-QINU%60%22'%7F(3)\n[3]: https://en.wikipedia.org/wiki/Rotation_matrix#General_rotations\n[Axis–angle representation]: https://en.wikipedia.org/wiki/Axis%E2%80%93angle_representation\n[Matrix multiplication]: https://en.wikipedia.org/wiki/Matrix_multiplication\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgbroques%2Ffreecad-rotations","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgbroques%2Ffreecad-rotations","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgbroques%2Ffreecad-rotations/lists"}