/* ======================================================================== * Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; namespace Robotics { /// /// A rigid body transform - a rotation followed by a translation. /// /// /// Applied to column vectors, so a point maps as p' = R p + t. USD authors a /// matrix4d in the row vector convention with the translation in the last row, /// which produces. /// public readonly struct RigidTransform : IEquatable { /// /// The transform that leaves a point where it is. /// public static RigidTransform Identity { get; } = new( 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0); /// /// The translation component, in metres. /// public (double X, double Y, double Z) Origin => (m_tx, m_ty, m_tz); /// /// Creates a pure translation. /// /// The X offset, in metres. /// The Y offset, in metres. /// The Z offset, in metres. /// The transform. public static RigidTransform Translation(double x, double y, double z) { return new RigidTransform( 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, x, y, z); } /// /// Creates a rotation about the X axis. /// /// The angle, in degrees. /// The transform. public static RigidTransform RotationX(double degrees) { double r = degrees * (Math.PI / 180.0); double c = Math.Cos(r); double s = Math.Sin(r); return new RigidTransform( 1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c, 0.0, 0.0, 0.0); } /// /// Creates a rotation about the Y axis. /// /// The angle, in degrees. /// The transform. public static RigidTransform RotationY(double degrees) { double r = degrees * (Math.PI / 180.0); double c = Math.Cos(r); double s = Math.Sin(r); return new RigidTransform( c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c, 0.0, 0.0, 0.0); } /// /// Creates a rotation about the Z axis. /// /// The angle, in degrees. /// The transform. public static RigidTransform RotationZ(double degrees) { double r = degrees * (Math.PI / 180.0); double c = Math.Cos(r); double s = Math.Sin(r); return new RigidTransform( c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0); } /// /// Applies in this transform's frame. /// /// The transform expressed in this frame. /// The composed transform. public RigidTransform Compose(in RigidTransform child) { return new RigidTransform( (m_r00 * child.m_r00) + (m_r01 * child.m_r10) + (m_r02 * child.m_r20), (m_r00 * child.m_r01) + (m_r01 * child.m_r11) + (m_r02 * child.m_r21), (m_r00 * child.m_r02) + (m_r01 * child.m_r12) + (m_r02 * child.m_r22), (m_r10 * child.m_r00) + (m_r11 * child.m_r10) + (m_r12 * child.m_r20), (m_r10 * child.m_r01) + (m_r11 * child.m_r11) + (m_r12 * child.m_r21), (m_r10 * child.m_r02) + (m_r11 * child.m_r12) + (m_r12 * child.m_r22), (m_r20 * child.m_r00) + (m_r21 * child.m_r10) + (m_r22 * child.m_r20), (m_r20 * child.m_r01) + (m_r21 * child.m_r11) + (m_r22 * child.m_r21), (m_r20 * child.m_r02) + (m_r21 * child.m_r12) + (m_r22 * child.m_r22), (m_r00 * child.m_tx) + (m_r01 * child.m_ty) + (m_r02 * child.m_tz) + m_tx, (m_r10 * child.m_tx) + (m_r11 * child.m_ty) + (m_r12 * child.m_tz) + m_ty, (m_r20 * child.m_tx) + (m_r21 * child.m_ty) + (m_r22 * child.m_tz) + m_tz); } /// /// Flattens the transform into the 16 row-major doubles a USD /// matrix4d attribute carries, with the translation in the last row. /// /// The flattened matrix. public double[] ToUsdRowMajor() { return [ m_r00, m_r10, m_r20, 0.0, m_r01, m_r11, m_r21, 0.0, m_r02, m_r12, m_r22, 0.0, m_tx, m_ty, m_tz, 1.0 ]; } /// public bool Equals(RigidTransform other) { return m_r00.Equals(other.m_r00) && m_r01.Equals(other.m_r01) && m_r02.Equals(other.m_r02) && m_r10.Equals(other.m_r10) && m_r11.Equals(other.m_r11) && m_r12.Equals(other.m_r12) && m_r20.Equals(other.m_r20) && m_r21.Equals(other.m_r21) && m_r22.Equals(other.m_r22) && m_tx.Equals(other.m_tx) && m_ty.Equals(other.m_ty) && m_tz.Equals(other.m_tz); } /// public override bool Equals(object? obj) { return obj is RigidTransform other && Equals(other); } /// public override int GetHashCode() { return HashCode.Combine(m_r00, m_r11, m_r22, m_tx, m_ty, m_tz); } /// /// Compares two transforms. /// /// The first transform. /// The second transform. /// true when the transforms are equal. public static bool operator ==(RigidTransform left, RigidTransform right) { return left.Equals(right); } /// /// Compares two transforms. /// /// The first transform. /// The second transform. /// true when the transforms differ. public static bool operator !=(RigidTransform left, RigidTransform right) { return !left.Equals(right); } private RigidTransform( double r00, double r01, double r02, double r10, double r11, double r12, double r20, double r21, double r22, double tx, double ty, double tz) { m_r00 = r00; m_r01 = r01; m_r02 = r02; m_r10 = r10; m_r11 = r11; m_r12 = r12; m_r20 = r20; m_r21 = r21; m_r22 = r22; m_tx = tx; m_ty = ty; m_tz = tz; } private readonly double m_r00; private readonly double m_r01; private readonly double m_r02; private readonly double m_r10; private readonly double m_r11; private readonly double m_r12; private readonly double m_r20; private readonly double m_r21; private readonly double m_r22; private readonly double m_tx; private readonly double m_ty; private readonly double m_tz; } /// /// Forward kinematics of the sample robot, derived from the link offsets authored in /// robot.usda. /// /// /// The server drives the arm by publishing six axis positions, and the connector maps /// each onto the matching xformOp:rotate* in the asset. Computing the tool centre /// point from those same six values - rather than from the choreography that produced /// them - is what keeps a carried part welded to the gripper instead of drifting away /// from it. /// public static class RobotKinematics { /// /// The number of axes the arm has. /// public const int AxisCount = 6; /// /// The reach of the arm measured along the link offsets, in metres. /// /// /// The sum of the J2, J3, J4 and flange X offsets. The cell layout keeps two robots /// further apart than this whenever both arms are deployed. /// public const double MaximumReach = 0.260 + 0.680 + 0.670 + 0.158; /// /// The offset from the flange to the point between the gripper jaws, in metres. /// public const double ToolCentrePointOffsetX = 0.150; /// /// Computes the pose of the point between the gripper jaws. /// /// The pose of the robot mount prim in cell coordinates. /// The six axis positions, in degrees. /// The tool centre point pose in cell coordinates. /// /// does not carry values. /// public static RigidTransform ComputeToolCentrePoint( in RigidTransform mount, ReadOnlySpan axesDegrees) { if (axesDegrees.Length != AxisCount) { throw new ArgumentException( $"Expected {AxisCount} axis positions.", nameof(axesDegrees)); } RigidTransform pose = mount .Compose(RigidTransform.Translation(0.0, 0.0, 0.3650)) .Compose(RigidTransform.Translation(0.0, 0.0, 0.2900)) .Compose(RigidTransform.RotationZ(axesDegrees[0])) .Compose(RigidTransform.Translation(0.2600, 0.0, 0.3850)) .Compose(RigidTransform.RotationY(axesDegrees[1])) .Compose(RigidTransform.Translation(0.6800, 0.0, 0.0)) .Compose(RigidTransform.RotationY(axesDegrees[2])) .Compose(RigidTransform.Translation(0.6700, 0.0, -0.0350)) .Compose(RigidTransform.RotationX(axesDegrees[3])) .Compose(RigidTransform.RotationY(axesDegrees[4])) .Compose(RigidTransform.RotationX(axesDegrees[5])) .Compose(RigidTransform.Translation(0.1580, 0.0, 0.0)); return pose.Compose( RigidTransform.Translation(ToolCentrePointOffsetX, 0.0, 0.0)); } /// /// Builds the pose of a robot mount from its cell position and heading. /// /// The X position, in metres. /// The Y position, in metres. /// The Z position, in metres. /// The heading about Z, in degrees. /// The mount pose. public static RigidTransform CreateMountPose( double x, double y, double z, double headingDegrees) { return RigidTransform.Translation(x, y, z) .Compose(RigidTransform.RotationZ(headingDegrees)); } } }