# How to find the rigid transformation data for External Data?

Member, Moderator, Employee Posts: 107
✭✭✭✭
edited June 2023

External Data allows to enter a rigid transformation in the global coordinate system in order to orient data that could be misaligned with the geometry. Finding the proper angles and translation data is not obvious when the data is not oriented along the axis of the global CS and could require a long "trial and error" process.

How can we automate this within Mechanical? The assumption is that we have the "geometry" of the data to be imported either in geometric form or as STL or as a mesh.

Tagged:

• Member, Moderator, Employee Posts: 107
✭✭✭✭

Here's a proposal. For comprehensiveness, it is assumed that the user has been able to get a representation of the data to be imported as geometry in Mechanical (either from a CAD/STL file or a mesh). The geometry is used to visually check the alignment. Yet it is not mandatory, as the script will only compute the transformation if no body is selected.

The principle is the following: the user will define two coordinate systems to be aligned. One is created on the target geometry (the one on which data will be mapped) and another one on the data to be imported. The user then selects in the tree both coordinate systems and the body to be moved (the one correspionding the data to be imported). A Part Transform will be created to show how the alignment has been achieved. If no body is selected, a message box with the rigid transformation between the two coordinate systems is displayed.

```# Compute Rigid Transformation between two coordinate systems
# Optionally create a Part transform on a body
# Created by P. Thieffry
# Last update: 2021/04/07

import math
import units

from Ansys.UI.Toolkit import *

# Utility functions

def dotProduct(v1,v2):
return v2[0]*v1[0]+v2[1]*v1[1]+v2[2]*v1[2]

def matProduct(m1,m2):
nr1=len(m1)
nc1=len(m1[0])
nc2=len(m2[0])
resM = [([0]*nc2) for i in range(nr1)]
for i in range(0,nr1):
for j in range(0,nc2):
val=0.
for k in range(0,nc1):
val+=resM[i][j]+m1[i][k]*m2[k][j]
resM[i][j]=val
return resM

def InverseTransformationMatrix(m):
mt=[[0.,0.,0.,0.],[0.,0.,0.,0.],[0.,0.,0.,0.],[0.,0.,0.,0.]]
for i in range(0,4):
for j in range(0,4):
mt[i][j]=m[j][i]

tVec=matProduct(mt,[[-m[0][3]],[-m[1][3]],[-m[2][3]],[-m[3][3]]])

for i in range(0,4):
mt[i][3]=tVec[i][0]
mt[3][i]=0.
mt[3][3]=1.
return mt

def CSBodyDistance(csys,body):
op_unit=csys.OriginX.Unit
orX=units.ConvertUnit(csys.OriginX.Value, fromUnit=op_unit, toUnit='m')
orY=units.ConvertUnit(csys.OriginY.Value, fromUnit=op_unit, toUnit='m')
orZ=units.ConvertUnit(csys.OriginZ.Value, fromUnit=op_unit, toUnit='m')
dist=(body.Centroid[0]-orX)**2+(body.Centroid[1]-orY)**2+(body.Centroid[2]-orZ)**2
return dist

def CreateSelection(body):
# Create selection from body
ExtAPI.SelectionManager.ClearSelection()
temp_sel= ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities)
temp_sel.Ids=[body.Id]
ExtAPI.SelectionManager.NewSelection(temp_sel)

def CreateCSOnBody(body):
# Create coordinate system on body so axes are along the axis of inertia of the body
CreateSelection(body)
csGroup=ExtAPI.DataModel.GetObjectsByType(DataModelObjectCategory.CoordinateSystems)[0]
newCS.Name='CS_body_'+body.Name
#    newCS.OriginDefineBy=Ansys.Mechanical.DataModel.Enums.CoordinateSystemAlignmentType.Fixed
return newCS

def anglesFromTransMatrix(tmat):
pi=math.pi
sY=-tmat[2][0]

if sY < 1.:
if (sY > -1.):
rotY=math.asin(sY)
rotX = math.atan2(tmat[2][1],tmat[2][2])
rotZ = math.atan2(tmat[1][0],tmat[0][0])
else: # sY = -1
rotY= -pi/2
rotX = -math.atan2(tmat[1][0],tmat[1][1])
rotZ = 0.
else: # sY=1
rotY= pi/2
rotX = -math.atan2(tmat[0][1],tmat[1][1])
rotZ = 0.

return rotX*180./pi,rotY*180./pi,rotZ*180./pi

def TransformationMatrix(origCS,targCS):
# compute transformation matrix to align origCS to targCS
# All coordinates are expressed in the global CS
#
# Retrieve CS origins in m
op_unit=origCS.OriginX.Unit
orX=units.ConvertUnit(origCS.OriginX.Value, fromUnit=op_unit, toUnit='m')
orY=units.ConvertUnit(origCS.OriginY.Value, fromUnit=op_unit, toUnit='m')
orZ=units.ConvertUnit(origCS.OriginZ.Value, fromUnit=op_unit, toUnit='m')
taX=units.ConvertUnit(targCS.OriginX.Value, fromUnit=op_unit, toUnit='m')
taY=units.ConvertUnit(targCS.OriginY.Value, fromUnit=op_unit, toUnit='m')
taZ=units.ConvertUnit(targCS.OriginZ.Value, fromUnit=op_unit, toUnit='m')

tMat=[[dotProduct(origCS.XAxis,targCS.XAxis),dotProduct(origCS.XAxis,targCS.YAxis),dotProduct(origCS.XAxis,targCS.ZAxis),-orX+taX],
[dotProduct(origCS.YAxis,targCS.XAxis),dotProduct(origCS.YAxis,targCS.YAxis),dotProduct(origCS.YAxis,targCS.ZAxis),-orY+taY],
[dotProduct(origCS.ZAxis,targCS.XAxis),dotProduct(origCS.ZAxis,targCS.YAxis),dotProduct(origCS.ZAxis,targCS.ZAxis),-orZ+taZ],
[0.,0.,0.,1.]]

return tMat

def TransformationData(origCS,targCS):
# compute angles and translations to be used in part transforms
# based on transformation matrix between two CS
#

csGroup=ExtAPI.DataModel.GetObjectsByType(DataModelObjectCategory.CoordinateSystems)[0]
globalCS=csGroup.Children[0] # get global CS as this is the one we want angles and translations expressed in

# First transform from globalCS to origCS
mGlobalToOrig=TransformationMatrix(globalCS,origCS)

# Compute inverse transformation
mOrigToGlobal=InverseTransformationMatrix(mGlobalToOrig)
# Transfom from global to targ
mGlobalToTarg=TransformationMatrix(globalCS,targCS)
# Now combine them
mOrigToTarg=matProduct(mGlobalToTarg,mOrigToGlobal)

# Now retrieve angle for transformation in global CS
rotX,rotY,rotZ=anglesFromTransMatrix(mOrigToTarg)
# Get translations in global CS
trX=mOrigToTarg[0][3]
trY=mOrigToTarg[1][3]
trZ=mOrigToTarg[2][3]

return trX,trY,trZ,rotX,rotY,rotZ

def CreateTransform(body,bodyCS,targCS):
# Transform body from bodyCS to targCS
# bodyCS and targCS should be oriented along axes of inertia
# Angles and translations to be computed based on global Coordinate System so they can be reused in External Data
#
csGroup=ExtAPI.DataModel.GetObjectsByType(DataModelObjectCategory.CoordinateSystems)[0]
globalCS=csGroup.Children[0] # get global CS as this is the one we want angles and translations expressed in

# get translations and angles
tX,tY,tZ,rotX,rotY,rotZ=TransformationData(bodyCS,targCS)

# create part transform
# first check if Transforms group exist, if not create it with a dummy part transform
trGroup=ExtAPI.DataModel.GetObjectsByType(DataModelObjectCategory.PartTransformGroup)
if len(trGroup)==0:
dummyT.Delete()
trGroup=ExtAPI.DataModel.GetObjectsByType(DataModelObjectCategory.PartTransformGroup)[0]
# create part transform
# Set geometry scoping on body
CreateSelection(body)
newTR.Location=ExtAPI.SelectionManager.CurrentSelection

# All data expressed in globalCS for reuse in External Data
newTR.CoordinateSystem=globalCS

# Set transformation data
newTR.TranslationX=Quantity(tX,'m')
newTR.TranslationY=Quantity(tY,'m')
newTR.TranslationZ=Quantity(tZ,'m')

newTR.RotationX=Quantity(rotX,'deg')
newTR.RotationY=Quantity(rotY,'deg')
newTR.RotationZ=Quantity(rotZ,'deg')

# transform gemetry
newTR.TransformGeometry()

return newTR

curSel=ExtAPI.DataModel.Tree.ActiveObjects

# Need to have two CS selected, otherwise stop
mess_line1 = 'Please select at least 2 coordinate systems in the tree and a maximum of one body if you want to visually check the transformation'
if (curSel.Count<2):
MessageBox.Show(mess_line1)
pass
else:
bodies=[]
cSyst=[]
for sel in curSel:
if sel.GetType()==Ansys.ACT.Automation.Mechanical.Body:
bodies.append(sel.GetGeoBody())
if sel.GetType()==Ansys.ACT.Automation.Mechanical.CoordinateSystem:
cSyst.append(sel)

if cSyst.Count!=2:
MessageBox.Show(mess_line1)
pass

if cSyst.Count==2 and bodies.Count>1:
MessageBox.Show(mess_line1)
pass

if bodies.Count == 1: # a body has been selected, we'll create a part transform
#find CS closest to selected body

d1=CSBodyDistance(cSyst[0],bodies[0])
d2=CSBodyDistance(cSyst[1],bodies[0])

if d1<d2:
origCS=cSyst[0]
targCS=cSyst[1]

else:
origCS=cSyst[1]
targCS=cSyst[0]

CreateTransform(bodies[0],origCS,targCS)
else: # no body has been selected, just compute transformation data and display in a message box
origCS=cSyst[0] # first selected CS is the one corresponding to external data
targCS=cSyst[1] # second CS is the one corresponding to actual geometry to be mapped on
tX,tY,tZ,rotX,rotY,rotZ=TransformationData(origCS,targCS)
op_Unit=origCS.OriginX.Unit

mess_line = 'Computed data for rigid transformation:\n\n'

mess_line+='  Orign X : '+str(Quantity(units.ConvertUnit(tX, fromUnit='m', toUnit=op_Unit),op_Unit))+'\n'
mess_line+='  Orign Y : '+str(Quantity(units.ConvertUnit(tY, fromUnit='m', toUnit=op_Unit),op_Unit))+'\n'
mess_line+='  Orign Z : '+str(Quantity(units.ConvertUnit(tZ, fromUnit='m', toUnit=op_Unit),op_Unit))+'\n'
mess_line+='\n'
mess_line+='  Rotation X : '+ str(Quantity(rotX,'deg'))+'\n'
mess_line+='  Rotation Y : '+ str(Quantity(rotY,'deg'))+'\n'
mess_line+='  Rotation Z : '+ str(Quantity(rotZ,'deg'))+'\n'

MessageBox.Show(mess_line)

```
• Member, Moderator, Employee Posts: 107
✭✭✭✭

Erratum: I realized the order of transformations is NOT the same in Mechanical and External data. A part transform in Mechanical will do RX, RY, RZ and then translations. External data does RY RX RZ then translations (RX and RY are in reverse order). So the following script will compute both configurations and display the ones for External Data in a message box.

BTW, it will also copy the data to the clipboard for further reuse.

```# Compute Rigid Transformation between two coordinate systems
# Optionally create a Part transform on a body
# Created by P. Thieffry
# Last update: 2021/04/07

import math
import units
import clr

from Ansys.UI.Toolkit import *

from System.Windows.Forms import Clipboard
def setText(text):
Clipboard.SetText(text)

def getText():
return Clipboard.GetText()

# Utility functions

def dotProduct(v1,v2):
return v2[0]*v1[0]+v2[1]*v1[1]+v2[2]*v1[2]

def matProduct(m1,m2):
nr1=len(m1)
nc1=len(m1[0])
nc2=len(m2[0])
resM = [([0]*nc2) for i in range(nr1)]
for i in range(0,nr1):
for j in range(0,nc2):
val=0.
for k in range(0,nc1):
val+=resM[i][j]+m1[i][k]*m2[k][j]
resM[i][j]=val
return resM

def InverseTransformationMatrix(m):
mt=[[0.,0.,0.,0.],[0.,0.,0.,0.],[0.,0.,0.,0.],[0.,0.,0.,0.]]
for i in range(0,4):
for j in range(0,4):
mt[i][j]=m[j][i]

tVec=matProduct(mt,[[-m[0][3]],[-m[1][3]],[-m[2][3]],[-m[3][3]]])

for i in range(0,4):
mt[i][3]=tVec[i][0]
mt[3][i]=0.
mt[3][3]=1.
return mt

def CSBodyDistance(csys,body):
op_unit=csys.OriginX.Unit
orX=units.ConvertUnit(csys.OriginX.Value, fromUnit=op_unit, toUnit='m')
orY=units.ConvertUnit(csys.OriginY.Value, fromUnit=op_unit, toUnit='m')
orZ=units.ConvertUnit(csys.OriginZ.Value, fromUnit=op_unit, toUnit='m')
dist=(body.Centroid[0]-orX)**2+(body.Centroid[1]-orY)**2+(body.Centroid[2]-orZ)**2
return dist

def CreateSelection(body):
# Create selection from body
ExtAPI.SelectionManager.ClearSelection()
temp_sel= ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities)
temp_sel.Ids=[body.Id]
ExtAPI.SelectionManager.NewSelection(temp_sel)

def CreateCSOnBody(body):
# Create coordinate system on body so axes are along the axis of inertia of the body
CreateSelection(body)
csGroup=ExtAPI.DataModel.GetObjectsByType(DataModelObjectCategory.CoordinateSystems)[0]
newCS.Name='CS_body_'+body.Name
#    newCS.OriginDefineBy=Ansys.Mechanical.DataModel.Enums.CoordinateSystemAlignmentType.Fixed
return newCS

def anglesFromTransMatrixXYZ(tmat):
pi=math.pi
sY=-tmat[2][0]

if sY < 1.:
if (sY > -1.):
rotY=math.asin(sY)
rotX = math.atan2(tmat[2][1],tmat[2][2])
rotZ = math.atan2(tmat[1][0],tmat[0][0])
else: # sY = -1
rotY= -pi/2
rotX = -math.atan2(tmat[1][0],tmat[1][1])
rotZ = 0.
else: # sY=1
rotY= pi/2
rotX = -math.atan2(tmat[0][1],tmat[1][1])
rotZ = 0.

return rotX*180./pi,rotY*180./pi,rotZ*180./pi

def anglesFromTransMatrixYXZ(tmat):
pi=math.pi
sX=tmat[2][1]

if sX < 1.:
if (sX > -1.):
rotX=math.asin(sX)
rotY = math.atan2(-tmat[2][0],tmat[2][2])
rotZ = math.atan2(-tmat[0][1],tmat[1][1])
else: # sX = -1
rotX= -pi/2
rotY = -math.atan2(tmat[1][0],tmat[1][2])
rotZ = 0.
else: # sX=1
rotX= pi/2
rotY = -math.atan2(tmat[1][0],tmat[1][2])
rotZ = 0.

return rotX*180./pi,rotY*180./pi,rotZ*180./pi

def TransformationMatrix(origCS,targCS):
# compute transformation matrix to align origCS to targCS
# All coordinates are expressed in the global CS
#
# Retrieve CS origins in m
op_unit=origCS.OriginX.Unit
orX=units.ConvertUnit(origCS.OriginX.Value, fromUnit=op_unit, toUnit='m')
orY=units.ConvertUnit(origCS.OriginY.Value, fromUnit=op_unit, toUnit='m')
orZ=units.ConvertUnit(origCS.OriginZ.Value, fromUnit=op_unit, toUnit='m')
taX=units.ConvertUnit(targCS.OriginX.Value, fromUnit=op_unit, toUnit='m')
taY=units.ConvertUnit(targCS.OriginY.Value, fromUnit=op_unit, toUnit='m')
taZ=units.ConvertUnit(targCS.OriginZ.Value, fromUnit=op_unit, toUnit='m')

tMat=[[dotProduct(origCS.XAxis,targCS.XAxis),dotProduct(origCS.XAxis,targCS.YAxis),dotProduct(origCS.XAxis,targCS.ZAxis),-orX+taX],
[dotProduct(origCS.YAxis,targCS.XAxis),dotProduct(origCS.YAxis,targCS.YAxis),dotProduct(origCS.YAxis,targCS.ZAxis),-orY+taY],
[dotProduct(origCS.ZAxis,targCS.XAxis),dotProduct(origCS.ZAxis,targCS.YAxis),dotProduct(origCS.ZAxis,targCS.ZAxis),-orZ+taZ],
[0.,0.,0.,1.]]

return tMat

def TransformationData(origCS,targCS):
# compute angles and translations to be used in part transforms
# based on transformation matrix between two CS
#

csGroup=ExtAPI.DataModel.GetObjectsByType(DataModelObjectCategory.CoordinateSystems)[0]
globalCS=csGroup.Children[0] # get global CS as this is the one we want angles and translations expressed in

# First transform from globalCS to origCS
mGlobalToOrig=TransformationMatrix(globalCS,origCS)

# Compute inverse transformation
mOrigToGlobal=InverseTransformationMatrix(mGlobalToOrig)
# Transfom from global to targ
mGlobalToTarg=TransformationMatrix(globalCS,targCS)
# Now combine them
mOrigToTarg=matProduct(mGlobalToTarg,mOrigToGlobal)

# Now retrieve angle for transformation in global CS
rotX,rotY,rotZ=anglesFromTransMatrixXYZ(mOrigToTarg)
# Get translations in global CS
trX=mOrigToTarg[0][3]
trY=mOrigToTarg[1][3]
trZ=mOrigToTarg[2][3]

rXExtData,rYExtData,rZExtData=anglesFromTransMatrixYXZ(mOrigToTarg)

return trX,trY,trZ,rotX,rotY,rotZ,rXExtData,rYExtData,rZExtData

def CreateTransform(body,bodyCS,targCS):
# Transform body from bodyCS to targCS
# bodyCS and targCS should be oriented along axes of inertia
# Angles and translations to be computed based on global Coordinate System so they can be reused in External Data
#
csGroup=ExtAPI.DataModel.GetObjectsByType(DataModelObjectCategory.CoordinateSystems)[0]
globalCS=csGroup.Children[0] # get global CS as this is the one we want angles and translations expressed in

# get translations and angles
tX,tY,tZ,rotX,rotY,rotZ,rXExtData,rYExtData,rZExtData=TransformationData(bodyCS,targCS)

# create part transform
# first check if Transforms group exist, if not create it with a dummy part transform
trGroup=ExtAPI.DataModel.GetObjectsByType(DataModelObjectCategory.PartTransformGroup)
if len(trGroup)==0:
dummyT.Delete()
trGroup=ExtAPI.DataModel.GetObjectsByType(DataModelObjectCategory.PartTransformGroup)[0]
# create part transform
# Set geometry scoping on body
CreateSelection(body)
newTR.Location=ExtAPI.SelectionManager.CurrentSelection

# All data expressed in globalCS for reuse in External Data
newTR.CoordinateSystem=globalCS

# Set transformation data
newTR.TranslationX=Quantity(tX,'m')
newTR.TranslationY=Quantity(tY,'m')
newTR.TranslationZ=Quantity(tZ,'m')

newTR.RotationX=Quantity(rotX,'deg')
newTR.RotationY=Quantity(rotY,'deg')
newTR.RotationZ=Quantity(rotZ,'deg')

# transform gemetry
newTR.TransformGeometry()

# return newly created object and rotations for ExternalData
return newTR,tX,tY,tZ,rotX,rotY,rotZ,rXExtData,rYExtData,rZExtData

curSel=ExtAPI.DataModel.Tree.ActiveObjects

# Need to have two CS selected, otherwise stop
mess_line1 = 'Please select at least 2 coordinate systems in the tree and a maximum of one body if you want to visually check the transformation'
if (curSel.Count<2):
MessageBox.Show(mess_line1)
pass
else:
bodies=[]
cSyst=[]
for sel in curSel:
if sel.GetType()==Ansys.ACT.Automation.Mechanical.Body:
bodies.append(sel.GetGeoBody())
if sel.GetType()==Ansys.ACT.Automation.Mechanical.CoordinateSystem:
cSyst.append(sel)

if cSyst.Count!=2:
MessageBox.Show(mess_line1)
pass

if cSyst.Count==2 and bodies.Count>1:
MessageBox.Show(mess_line1)
pass

if bodies.Count == 1: # a body has been selected, we'll create a part transform
#find CS closest to selected body

d1=CSBodyDistance(cSyst[0],bodies[0])
d2=CSBodyDistance(cSyst[1],bodies[0])

if d1<d2:
origCS=cSyst[0]
targCS=cSyst[1]

else:
origCS=cSyst[1]
targCS=cSyst[0]

pTrans,tX,tY,tZ,rotX,rotY,rotZ,rotXExtData,rotYExtData,rotZExtData=CreateTransform(bodies[0],origCS,targCS)

else: # no body has been selected, just compute transformation data and display in a message box
origCS=cSyst[0] # first selected CS is the one corresponding to external data
targCS=cSyst[1] # second CS is the one corresponding to actual geometry to be mapped on
tX,tY,tZ,rotX,rotY,rotZ,rotXExtData,rotYExtData,rotZExtData=TransformationData(origCS,targCS)

# Message Box with results
op_Unit=origCS.OriginX.Unit
orX=units.ConvertUnit(origCS.OriginX.Value, fromUnit='m', toUnit=op_Unit)

mess_line = 'Computed data for rigid transformation in External data:\n'

mess_line+='\tOrign X : '+str(Quantity(units.ConvertUnit(tX, fromUnit='m', toUnit=op_Unit),op_Unit))+'\n'
mess_line+='\tOrign Y : '+str(Quantity(units.ConvertUnit(tY, fromUnit='m', toUnit=op_Unit),op_Unit))+'\n'
mess_line+='\tOrign Z : '+str(Quantity(units.ConvertUnit(tZ, fromUnit='m', toUnit=op_Unit),op_Unit))+'\n'
mess_line+='\n'
mess_line+='\tRotation Theta XY :'+ str(Quantity(rotZExtData,'deg'))+'\n'
mess_line+='\tRotation Theta YZ :'+ str(Quantity(rotXExtData,'deg'))+'\n'
mess_line+='\tRotation Theta ZX :'+ str(Quantity(rotYExtData,'deg'))+'\n'
MessageBox.Show(mess_line)

Clipboard.SetText(mess_line)
```