Direction Cosine Matrix To Rotation Angles — Robotics/Orientation 3D
Robotics/Orientation_3D/Direction_Cosine_Matrix_To_Rotation_Angles · 1 input / 1 output port(s) at insert · exports to Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text
Description#
The block's own DESCRIPTION_HTML, rendered verbatim — the same text the config dialog's info panel and the library navigator show. Fix a wrong sentence in the block's .cpp (R-D9), never here.
Direction Cosine Matrix To Rotation Angles
Robotics / Orientation 3D
Reads the three rotation angles [r1; r2; r3] of a chosen rotation order back out of the [3,3] direction cosine matrix D they describe. It is the inverse of Rotation Angles To Direction Cosine Matrix, and the angles come out in the order they are applied – r1 first.
Writing (a, b, c) for the axes the order names and ε = +1 when b is the cyclic successor of a (−1 otherwise), the extraction is one rule with two branches:
- Tait-Bryan (three different axes, e.g. ZYX): r2 = asin(εDca), r1 = atan2(−εDcb, Dcc), r3 = atan2(−εDba, Daa).
- Proper Euler (first and third axes equal, e.g. ZYZ, with d the axis the order never names): r2 = acos(Daa), r1 = atan2(Dab, −εDad), r3 = atan2(Dba, εDda).
Only five of the nine entries are ever read, so the cost does not depend on the order.
Ports
- D – the direction cosine matrix, [3,3]. The size is fixed. It is the passive matrix, as everything in this family called a direction cosine matrix is – the transpose of what Quaternion To Rotation Matrix returns.
- angles – the three angles in radians, a [3,1] column [r1; r2; r3]. Its size is fixed and does not follow the input's.
Parameters
- Rotation Order – which three axes the angles turn about, and in which order
r1, r2, r3 are applied. Twelve values, the
same twelve the Simulink block offers and with the same names:
- ZYX – the default, and the aerospace yaw–pitch–roll set.
- ZXY, YXZ, YZX, XYZ, XZY – the other five Tait-Bryan orders. The middle angle comes from an asin and lies in [−π/2, π/2].
- ZYZ, ZXZ, YXY, YZY, XYX, XZX – the six proper Euler orders. The middle angle comes from an acos and lies in [0, π].
- Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.
Code export
All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text.
The rotation order is structural and is baked into the exported body: it decides which five entries the code reads, so it is not offered as a tunable parameter on the generated core. Changing it means exporting again.
On the three HDL targets the block is offered as simulation-only: a Q16.16 datapath has no inverse trigonometric function, so all of it goes through real arithmetic and only the ports quantize. This is the same choice Quaternion To Euler makes.
Simulink bridge
Both directions, onto
aerolibtransform2/Direction Cosine Matrix to Rotation Angles in the Aerospace
Blockset. One parameter pair crosses: Rotation Order →
rotationOrder, and the translation is lossless in both directions because
the twelve names are identical on the two sides. Its other two dialog parameters do not
cross and are listed as ignored: action and tolerance select what the
Simulink block does when the matrix is not orthonormal, and this block validates nothing.
The block's name is drawn on two lines, so the real library path carries an
embedded newline between Matrix and to; the flattened one-line
spelling resolves to nothing. And it defines no SampleTime parameter, so the
rate stays on this side and a block configured with an explicit positive rate reports that the
rate did not cross.
The Simulink block returns the angles as a row; this one returns a column, as every vector port in this family does.
Notes
- Algebraic, with no state: the output depends only on the current input and the configured order.
- A proper-Euler middle angle is never negative. acos returns [0, π], so a rotation whose natural middle angle is negative comes back as its positive twin with r1 and r3 shifted by π – the same rotation, a different representative. MATLAB does exactly the same thing.
- Angles are radians, never degrees, on both sides of the bridge.
- Four of the nine inputs never reach the arithmetic, whatever the order. That is the formula rather than an oversight, and it is worth knowing when reading a comparison: a disagreement confined to an entry this order does not use cannot show up here at all.
- Gimbal lock is not signalled. When the middle angle reaches the end of its range the first and third stop being separately determined and both atan2 calls approach atan2(0, 0), which every target answers as 0. The result is still a valid representative of the rotation.
- The asin and acos arguments are clamped to [−1, 1]. On an exact rotation matrix they are already inside it; the clamp is what keeps rounding at the boundary from producing NaN in six targets, a complex number in MATLAB and an aborted simulation in VHDL.
- Orthonormality is not checked. The formula is evaluated on whatever arrives.
- Deliberately no state space – the map is not linear.
Code facts#
| Fact | Value |
|---|---|
| registered type | Robotics/Orientation_3D/Direction_Cosine_Matrix_To_Rotation_Angles |
| family | Robotics/Orientation_3D |
| solver environment class | ICoreBlock_0_Robotics_1_Orientation_3D_2_Direction_Cosine_Matrix_To_Rotation_Angles |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Robotics/Orientation_3D/Direction_Cosine_Matrix_To_Rotation_Angles/ICoreBlock_0_Robotics_1_Orientation_3D_2_Direction_Cosine_Matrix_To_Rotation_Angles.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Robotics/Orientation_3D/Direction_Cosine_Matrix_To_Rotation_Angles/ICoreBlock_0_Robotics_1_Orientation_3D_2_Direction_Cosine_Matrix_To_Rotation_Angles.h |
| default size on canvas | 150 × 76 px |
| ports at insert | 1 in, 1 out |
| code generators implemented | Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text |
Ports#
| # | Direction | Signal type | Description label |
|---|---|---|---|
| 1 | in | ICoreDouble | D |
| 2 | out | ICoreDouble | angles |
Ports the constructor creates. A block whose port list changes with its configuration adds or removes ports at load time; the count above is the one a freshly inserted block has.
Configuration variables#
| Config variable | Default | Simulink parameter |
|---|---|---|
Rotation Order | combo | — |
Every block also carries Sampling Time (s) from ICoreBlockSolverEnvironment: zero or less inherits the solver's rate, a positive value runs the block at that period.
Simulink bridge#
| support | Support::Both |
| Simulink path | aerolibtransform2/Direction Cosine Matrix\nto Rotation Angles |
| port-count rule | PortsParam::None |
SampleTime parameter | no — the counterpart defines none; the rate stays on the ICore side |
| ICore config | Simulink parameter | Value translation |
|---|---|---|
CONFIG_ORDER.c_str() | rotationOrder | ZYX → ZYX, ZYZ → ZYZ, ZXY → ZXY, ZXZ → ZXZ, YXZ → YXZ, YXY → YXY, YZX → YZX, YZY → YZY, XYZ → XYZ, XYX → XYX, XZY → XZY, XZX → XZX |
Caveat (shown to the user): MEASURED 2026-09-10: the Simulink block carries rotationOrder with the same twelve names this block offers, so the translation is one-to-one and lossless both ways, plus 'action' (None/Warning/Error) and 'tolerance', which decide what it does when the matrix is not orthonormal. Neither of those crosses: this block validates nothing. It returns the angles as a row where this block returns a column, and it defines no SampleTime parameter: an ICore rate set explicitly stays on this side
Catalog contract: src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h
Description vs code#
The checker has a blind spot here — it could not resolve something (a grouped port bullet, a computed config name), which is reported and never counted as a pass. A reader has to settle it:
B01 Simulink params rule(s) this tool cannot resolveB0every stimulus in the sample errored — cross-checks skipped
The verdict above is
tools/docs/check_block_descriptions.py(P7.1), which compares LISTS. It cannot read a sentence: "stateless" on a block with a state, an initial-value semantic the recursion does not implement, a "not synthesizable" caveat the HDL banner contradicts. That is the agent audit (P7.3) on BLOCK_DESCRIPTION_AUDIT.md, and this tool's green is not a substitute for one.
File banner (developer view)#
The top comment of the block's .cpp — the maths, the realization and the export strategy, addressed to whoever changes it. It must not contradict the description above (P7.5).
Direction Cosine Matrix To Rotation Angles — [3,3] to [r1; r2; r3] in a chosen order The standard extraction, in one rule for all twelve orders. With (a, b, c) the axis indices of the order's three letters and eps = +1 when b is the cyclic successor of a:
a /= c : r2 = asin(clamp(eps*D(c,a))) r1 = atan2(-eps*D(c,b), D(c,c)) r3 = atan2(-eps*D(b,a), D(a,a)) a == c : r2 = acos(clamp(D(a,a))) (d = the axis the order never names) r1 = atan2(D(a,b), -eps*D(a,d)) r3 = atan2(D(b,a), eps*D(d,a))
MEASURED against aerolibtransform2/Direction Cosine Matrix to Rotation Angles (R2026a, 2026-09-10): on the matrix of (1.05, -0.28, 0.41) at ZYX it answers those three angles back. The rule itself was checked against MATLAB's dcm2angle for ALL TWELVE orders on the matrix of (0.41, -0.73, 1.17): every one agreed EXACTLY.
⚠ THE RULE HAS ONE TEXTUAL FORM, angleExprs() below, and all ten generators read it. compute_h carries the numeric twin - strings and doubles cannot be one function - written in the same order with the same names so the two read side by side. It is the same rule Rodrigues_To_Rotation_Angles applies to a matrix it builds for itself; this block is handed the matrix instead.
⚠ ONLY FIVE OF THE NINE ENTRIES ARE READ. Four inputs never reach the arithmetic, whatever the order - which is why the emitted body does not grow with the order and why a rig has to drive a matrix whose relevant entries actually move.
⚠ ACOS RETURNS [0, pi], so a proper-Euler middle angle is never negative; MATLAB behaves the same way, measured.
⚠ SIMULATION-ONLY on the three HDL targets: three inverse trigonometric functions.
Sample results#
No stimulus produced a sampled output in this rig — Invalid input size at Direction Cosine Matrix To Rotation Angles block: ICore Blocks/Home/Direction Cosine Matrix To Rotation Angles. That is a fact about the single-block rig, not a verdict on the block: an offline batch fit, a block whose output only appears at onSolverFinish, or one that needs a driven environment cannot be exercised alone.
Category unsampled · sample time 0.1 · 60 steps · commit c29996956 · produced by docsSample --out <folder> --blocks Direction_Cosine_Matrix_To_Rotation_Angles Rotation_Matrix_To_Alpha_Beta Rotation_Matrix_To_Latitude_Longitude Rotation_Matrix_To_Wind_Angles --steps 60
Sample data: docs/generated/samples/Robotics__Orientation_3D__Direction_Cosine_Matrix_To_Rotation_Angles.json