Using Matrix Transformation to Map Between Tetrahedrons

Using Matrix Transformation to Map Between Tetrahedrons

strimbleL72VJ
Explorer Explorer
620 Views
3 Replies
Message 1 of 4

Using Matrix Transformation to Map Between Tetrahedrons

strimbleL72VJ
Explorer
Explorer

I'm posting a smaller sample of a larger Add In I'd like to create.

 

In this example, the script is expecting the user to select a body defined as a unit tetrahedron (i.e. a regular tetrahedron whose points all lie on the surface of a sphere with a radius of 1). Ideally, since it expects the position of these points, it should be able to use a matrix transform to map the body to another arbitrary tetrahedron.

 

In this script, the four source vertices and the four target vertices are known, and I need to get the transformation matrix that maps the source to the target. Hopefully this is a simple misunderstanding of the matrix algebra needed to solve for the transformation matrix. Any thoughts would be greatly appreciated.

 

 

import adsk.core, adsk.fusion, adsk.cam, traceback, pathlib
from math import sqrt

SOURCE_TETRAHEDRON = [
     sqrt(8/9), -sqrt(2/9), -sqrt(2/9), 0.0,
     0.0      , -sqrt(2/3),  sqrt(2/3), 0.0,
    -1/3      , -1/3      , -1/3      , 1.0,
     1.0      ,  1.0      ,  1.0      , 1.0,
]
TARGET_TETRAHEDRON = [
    2.0, 3.0, 6.0, 4.0,
    2.0, 3.0, 0.0, 2.0,
    2.0, 3.0, 9.0, 0.0,
    1.0, 1.0, 1.0, 1.0,
]
INVERSE_SOURCE = adsk.core.Matrix3D.create()
INVERSE_SOURCE.setWithArray(SOURCE_TETRAHEDRON)
INVERSE_SOURCE.invert()

TRANSFORMATION_MATRIX = adsk.core.Matrix3D.create()
TRANSFORMATION_MATRIX.setWithArray(TARGET_TETRAHEDRON)

TRANSFORMATION_MATRIX.transformBy(INVERSE_SOURCE)

class ScriptConfig:
    def __init__(self):
        self.pwd = pathlib.Path(__file__).parent.resolve()
        self.app = adsk.core.Application.get()
        self.ui = self.app.userInterface
        self.design = adsk.fusion.Design.cast(self.app.activeProduct)
        self.brep_manager = adsk.fusion.TemporaryBRepManager.get()

def run(context):
    try:
        config = ScriptConfig()

        selected_entity = config.ui.selectEntity("Select Tetrahedron", "Bodies").entity
        selected_body = adsk.fusion.BRepBody.cast(selected_entity)

        new_base_feature = config.design.activeComponent.features.baseFeatures.add()
        new_base_feature.name = "TransformTetrahedron"
        new_base_feature.startEdit()

        new_body = config.brep_manager.copy(selected_body)
        config.brep_manager.transform(new_body, TRANSFORMATION_MATRIX)
        config.design.activeComponent.bRepBodies.add(new_body, new_base_feature)

        new_base_feature.finishEdit()

    except:
        if config.ui:
            config.app.log('Failed:\n{}'.format(traceback.format_exc()))

 

 

0 Likes
621 Views
3 Replies
Replies (3)
Message 2 of 4

BrianEkins
Mentor
Mentor

You're on the right track, but the matrices you've defined for the input have some major problems. I've attached a presentation I made several years ago that discusses what a matrix is. I'm sure it could be clearer, but hopefully it helps. It was created for Inventor, but the principles are the same for Fusion.

 

Here's some untested code that I believe will do what you want. It starts with three points from the source and the equivalent three points from the target and then builds the matrix to transform the source to the target. The points I defined are random. The only requirement of the points is the three points that define either the source or target cannot be colinear.

 

# Define the three points of the source body.
sourcePoint1 = adsk.core.Point3D.create(5, 0, 0)
sourcePoint2 = adsk.core.Point3D.create(6, 3, 2)
sourcePoint3 = adsk.core.Point3D.create(3, 8, 3)

# Create the X, Y, and Z vectors of a coordinate system
# based on the source points.
xVec = sourcePoint1.vectorTo(sourcePoint2)
yVec = sourcePoint1.vectorTo(sourcePoint3)
zVec = xVec.crossProduct(yVec)

# Normalize the vectors.
xVec.normalize()
yVec.normalize()
zVec.normalize()

# Create a matrix using the vectors and point1 as the origin.
sourceMatrix = adsk.core.Matrix3d.create()
sourceMatrix.setWithCoordinateSystem(point2, xVec, YVec, zVec)

# Define the three points of the target body.
targetPoint1 = adsk.core.Point3D.create(2, 1, 3)
targetPoint2 = adsk.core.Point3D.create(-3, 2, 3)
targetPoint3 = adsk.core.Point3D.create(1, -2, 5)

# Create the X, Y, and Z vectors of a coordinate system
# based on the source points.
xVec = targetPoint1.vectorTo(targetPoint2)
yVec = targetPoint1.vectorTo(targetPoint3)
zVec = xVec.crossProduct(yVec)

# Normalize the vectors.
xVec.normalize()
yVec.normalize()
zVec.normalize()

# Create a matrix using the vectors and point1 as the origin.
targetMatrix = adsk.core.Matrix3d.create()
targetMatrix.setWithCoordinateSystem(point2, xVec, YVec, zVec)

# Invert the source matrix which will define a transform that will
# reposition the source body to the origin.
sourceMatrix.invert()

# Muliply the inverted source matrix with the target Matrix, which will 
# result in matrix that will position the source at the target. You can use
# this matrix in the TemporaryBRepManager.transform method.
sourceMatrix.transformBy(targetMatrix)

 

 

---------------------------------------------------------------
Brian Ekins
Inventor and Fusion 360 API Expert
Website/Blog: https://EkinsSolutions.com
Message 3 of 4

strimbleL72VJ
Explorer
Explorer

Thanks for the response! There's a lot of great info in your presentation.

 

Unfortunately, I don't think this fits my use case since I'm trying to map between two noncongruent tetrahedrons.

In this case, I'm not just trying to apply a translation and rotation to map the source and target together. I am also attempting to apply scaling and shearing. 

 

As a visual explanation, if I am given the two tetrahedrons shown below, I would like to understand how I can use the temporary brep manager to transform one into the other.

strimbleL72VJ_1-1719691138785.png

 

I adjusted my code to map between two coordinate systems. Interestingly, defining the coordinate axes as vectors from an origin to each of the three points without guaranteeing perpendicularity or normalizing the vectors produced the same results as setting source and target matrices from arrays. i.e. the two code samples below are effectively the same:

 

 

SOURCE_TETRAHEDRON = [
    sqrt(2/3), 0.0, sqrt(8/3), sqrt(2/3),
    sqrt(8/9)+sqrt(2/9), 0.0, 0.0, sqrt(2/9),
    0.0,  0.0,  0.0, 4/3,
    1.0,  1.0,  1.0, 1.0,
]
TARGET_TETRAHEDRON = [
    2.0, 3.0, 6.0, 4.0,
    2.0, 3.0, 2.0, 2.0,
    2.0, 3.0, 1.0, 0.0,
    1.0, 1.0, 1.0, 1.0,
]

SOURCE_POINTS = [
    adsk.core.Point3D.create(SOURCE_TETRAHEDRON[0], SOURCE_TETRAHEDRON[4], SOURCE_TETRAHEDRON[8]),
    adsk.core.Point3D.create(SOURCE_TETRAHEDRON[1], SOURCE_TETRAHEDRON[5], SOURCE_TETRAHEDRON[9]),
    adsk.core.Point3D.create(SOURCE_TETRAHEDRON[2], SOURCE_TETRAHEDRON[6], SOURCE_TETRAHEDRON[10]),
    adsk.core.Point3D.create(SOURCE_TETRAHEDRON[3], SOURCE_TETRAHEDRON[7], SOURCE_TETRAHEDRON[11]),
]
TARGET_POINTS = [
    adsk.core.Point3D.create(TARGET_TETRAHEDRON[0], TARGET_TETRAHEDRON[4], TARGET_TETRAHEDRON[8]),
    adsk.core.Point3D.create(TARGET_TETRAHEDRON[1], TARGET_TETRAHEDRON[5], TARGET_TETRAHEDRON[9]),
    adsk.core.Point3D.create(TARGET_TETRAHEDRON[2], TARGET_TETRAHEDRON[6], TARGET_TETRAHEDRON[10]),
    adsk.core.Point3D.create(TARGET_TETRAHEDRON[3], TARGET_TETRAHEDRON[7], TARGET_TETRAHEDRON[11]),
]

SOURCE_VECTORS = [
    SOURCE_POINTS[0].vectorTo(SOURCE_POINTS[1]),
    SOURCE_POINTS[0].vectorTo(SOURCE_POINTS[2]),
    SOURCE_POINTS[0].vectorTo(SOURCE_POINTS[3]),
]
TARGET_VECTORS = [
    TARGET_POINTS[0].vectorTo(TARGET_POINTS[1]),
    TARGET_POINTS[0].vectorTo(TARGET_POINTS[2]),
    TARGET_POINTS[0].vectorTo(TARGET_POINTS[3]),
]

TRANSFORMATION = adsk.core.Matrix3D.create()
TRANSFORMATION.setToAlignCoordinateSystems(
    SOURCE_POINTS[1],
    SOURCE_VECTORS[0],
    SOURCE_VECTORS[1],
    SOURCE_VECTORS[2],
    TARGET_POINTS[1],
    TARGET_VECTORS[0],
    TARGET_VECTORS[1],
    TARGET_VECTORS[2],
)
SOURCE_TETRAHEDRON = [
    sqrt(2/3), 0.0, sqrt(8/3), sqrt(2/3),
    sqrt(8/9)+sqrt(2/9), 0.0, 0.0, sqrt(2/9),
    0.0,  0.0,  0.0, 4/3,
    1.0,  1.0,  1.0, 1.0,
]
TARGET_TETRAHEDRON = [
    2.0, 3.0, 6.0, 4.0,
    2.0, 3.0, 2.0, 2.0,
    2.0, 3.0, 1.0, 0.0,
    1.0, 1.0, 1.0, 1.0,
]

SOURCE_MATRIX = adsk.core.Matrix3D.create()
TARGET_MATRIX = adsk.core.Matrix3D.create()

SOURCE_MATRIX.setWithArray(SOURCE_TETRAHEDRON)
TARGET_MATRIX.setWithArray(TARGET_TETRAHEDRON)

TRANSFORMATION = SOURCE_MATRIX
TRANSFORMATION.invert()
TRANSFORMATION.transformBy(TARGET_MATRIX)

 

 

0 Likes
Message 4 of 4

MichaelT_123
Advisor
Advisor

Hi Mr StrimbleL72VJ

 

Let's just do a little mental exercise.

What we want to find is … the smooth function, which converts the topological entity into its homotopic equivalence.

 

When the entity is defined by an ordered/unordered set of points and the category of the function is limited to linear ones only, the problem can be easily abstracted.

Assume that:

  • the source entity is defined by                                    {pᵢ} i∈<0,n>                         (1)
  • the destination entity is defined by                            {qⱼ } j∈<0,m>                      (2)
  • To progress with the exercise                                      n = m                                    (3)     is a must, and
  • There is one (or more) equivalence set between      𝓔(pᵢ, qⱼ) ≠ ∅                        (4)

The rest is easy.

Let’s 𝓜(.) be our homotopic matrix, then.                        𝓜(𝓔ₖ(pᵢ)) = qⱼ for i∈<0,n> (5)

Thus, we ended up with sets (by k) of linear equation systems that present a couple of cases.

  • The solution does not exist – there is no 𝓜(.) satisfying (5)
  • There is one solution for the particular k
  • Multiple solutions for k indicate some sort of symmetries in the system.

But this is not all.

If one is looking for the geometrical transformation solution (not only generally the linear one), the 𝓜(.) must satisfy such condition to qualify … but this is trivial.

What about 𝓔() defining points correspondence? Things can get complicated here. The number of permutations 𝓔() can produce will depend upon the type of topological entities we are acting upon: unordered, ordered, and ordered how. To make the issue clearer, consider how many permuted connections exist between

  • two sets of four points
  • two sets of tetrahedron mesh vertices
  • two sets of BRep tetrahedron vertices (normal of faces are normalized – pointing out)

In your case, where you have (probably) established some direction relation between your tetrahedrons, the number of permutations … should be 1.

Still, in general cases, it can grow very fast … in a factorial manner!

 

The code for the above should be as simple as my explanation.

 

Regards

MichaelT

MichaelT
0 Likes