22 Aug, 2019

Axis To Axis Transformations With EKL

Transforming geometric features from an AxisSystem to another is a common task in CATIA; especially when the goal is to decouple driving geometry from the construction of a UDF or a PowerCopy. For example, providing 2D-curves as input to a UDF that transforms the curves to build an extruded surface along a line–think window-frame tubes/channels. There are many such use cases. While EKL does not provide a convenient Axis To Axis transformation function, it does make available all the APIs we need to build our own function. Take a look:


Transforming a geometric features will depend on a source AxisSystem and a target AxisSystem. There are four types of transformations that we can apply to geometric objects: translation, rotation, scale, and skew. Our use case will require handling translation and rotation.

The idea is to extract the 3 rotation angles (around x, y, and z) and the origin from both axes. Then compute the difference in rotations between the corresponding 3 axes. Then identify the translation direction and distance. Finally, we\’ll rotation the geometric object around the source axis system\’s x direction, then take the resulting object and rotate it around the source\’s y direction, then take the resulting object and rotate it around the source\’s z direction. Then finally translate the result of the last rotation (around z) using the direction and distance– from source origin to target origin.

AxisSystems have two attributes: Origin and Axes. The first is a horizontal vector (1 row of 3 columns) with real numbers indicating x, y, and z coordinates. Notes that EKL uses mm for lengths. So the Real values are the magnitudes without the mm unit. Origin\’s vector is organized as follows:

x, y, z

The second is a matrix, 3 by 3, holding the x, y, and z horizontal vectors that define the AxisSystem orientation. Rows are organized as follows:

xx, xy, xz
yx, yy, yz
zx, zy, zz

The first row describes the X Axis, the second describes Y, and the third describes Z. Calculating the rotations around each axis can be done using the following formulas:

  • x rotation is equal to the atan2(xy, xx)
  • y rotation is equal to the atan2(yz, zz)
  • z rotation is equal to the asin(-xz)

Note that if you\’re using a version of CATIA that is older than 18X, then atan2 function might not be available. So instead, you\’ll need to use atan(number1/number2).

To see the content of a matrix, use the Dump() method. For example

let myMatrix(Matrix)
myMatrix =...
Notify( myMatrix.Dump())

Now let\’s take a look at the full code!


/* Action created by melkhaldi 8/20/2022 */
//inputs: SourceAxis: AxisSystem, TargetAxis: AxisSystem, and GeoObject: Wireframe

//get the source axis system vectors
let souceMatrix(Matrix)
souceMatrix = SourceAxis.Axes

//get the source axis system origin vector
let sourceOrigin(Vector)
sourceOrigin =SourceAxis.Origin

//build a point object, EKL uses millimeters by default --regardless of what your settings are in session
let sourcePoint(Point)
sourcePoint = point( sourceOrigin.Get(1,1)*1mm, sourceOrigin.Get(1,2)*1mm, sourceOrigin.Get(1,3)*1mm)

 //get source axis matrix components
let sourceXX, sourceXY, sourceXZ, sourceYX, sourceYY, sourceYZ, sourceZX, sourceZY, sourceZZ(Real)
sourceXX = souceMatrix.Get(1,1)
sourceXY = souceMatrix.Get(1,2)
sourceXZ = souceMatrix.Get(1,3)

sourceYX = souceMatrix.Get(2,1)
sourceYY = souceMatrix.Get(2,2)
sourceYZ = souceMatrix.Get(2,3)

sourceZX = souceMatrix.Get(3,1)
sourceZY = souceMatrix.Get(3,2)
sourceZZ = souceMatrix.Get(3,3)

//get the source rotation angles around each axis using the axis system matrix components
let sourceXRotation, sourceYRotation, sourceZRotation(Angle)
sourceZRotation = atan2(sourceXY, sourceXX)
sourceYRotation = atan2(sourceYZ, sourceZZ)
sourceXRotation = asin(-sourceXZ)   

//lets build Directions from the source matrix
let sourceXDir, sourceYDir, sourceZDir (Direction)
sourceXDir= direction(sourceXX*1mm, sourceXY*1mm, sourceXZ*1mm)
sourceYDir = direction(sourceYX*1mm, sourceYY*1mm, sourceYZ*1mm)
sourceZDir = direction(sourceZX*1mm, sourceZY*1mm, sourceZZ*1mm)

//let's create source axis system lines. we'll need those for the rotations later on.
let sourceXLine, sourceYLine, sourceZLine(Line)
//lets get some directions using a line. 
//the line is form axis origin to a point that is a result of a translate of the origin point to some distance along a direction
sourceXLine = line( sourcePoint, translate( sourcePoint, sourceXDir, 1mm):Point)
sourceYLine = line( sourcePoint, translate( sourcePoint, sourceYDir, 1mm):Point)
sourceZLine = line( sourcePoint, translate( sourcePoint, sourceZDir, 1mm):Point)

//get the target axis system vectors
let targetMatrix(Matrix)
targetMatrix = TargetAxis.Axes

//get target origin 
let targetOrigin(Vector)
targetOrigin =TargetAxis.Origin

//make a target axis system point at axis origin
let  targetPoint(Point)
targetPoint  = point( targetOrigin.Get(1,1)*1mm, targetOrigin.Get(1,2)*1mm, targetOrigin.Get(1,3)*1mm)

//get target axis system components
let targetXX, targetXY, targetXZ, targetYX, targetYY, targetYZ, targetZX, targetZY, targetZZ(Real)
targetXX = targetMatrix.Get(1,1)
targetXY = targetMatrix.Get(1,2)
targetXZ = targetMatrix.Get(1,3)

targetYX = targetMatrix.Get(2,1)
targetYY = targetMatrix.Get(2,2)
targetYZ = targetMatrix.Get(2,3)

targetZX = targetMatrix.Get(3,1)
targetZY = targetMatrix.Get(3,2)
targetZZ = targetMatrix.Get(3,3)

//get target axis system rotations
let targetXRotation, targetYRotation, targetZRotation(Angle)
targetZRotation = atan2(targetXY, targetXX)
targetYRotation = atan2(targetYZ, targetZZ)
targetXRotation = asin(- targetXZ)  

//apply rotation angles to the geometric object we need to transform.
//the result of each rotation will be the input for the next one
//note that we will do the rotations around the directions of the source.
//rotation is the target angle  - source angle
let rX, rY, rZ(Wireframe)
set rX = rotate( GeoObject,  sourceXLine, targetXRotation - sourceXRotation)
set rY = rotate( rX,  sourceYLine, targetYRotation- sourceYRotation)
set rZ = rotate( rY,  sourceZLine, targetZRotation- sourceZRotation)

//now lets translate
//get the direction from source origin point to target origin point
let direct(Direction)
direct = direction( line( sourcePoint, targetPoint))

//get the travel distance
let dist(Length)
dist = distance( sourcePoint, targetPoint)

//translate the last rotated shape

//let's try to give it a unique name
let ResultName(String)
ResultName = TextFormat("AxisToAxis #--#", GeoObject.Name,GeoObject.PrimaryType.Name())

//lets recreate the wireframe
let xFormed(Wireframe)
//note that I am declaring the variable using the super class, more generic type, Wireframe
//however, I am using the type name, which is a string, as the creation type for the new function
xFormed = new( GeoObject.PrimaryType.Name(), ResultName , GeoObject.Owner)
xFormed = translate( rZ,direct, dist)
Tags: , , ,

About : Maher Elkhaldi

Maher Elkhaldi is a senior applications engineer at Tesla Motors. He founded the 3DXAutomation blog to help make knowledge of programming CATIA easier to find, and contribute to the open-source community.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.