Source code for nukescripts.geomath

# Copyright (c) 2024 The Foundry Visionmongers Ltd.  All Rights Reserved.

import math
import _nukemath

"""
Mathematical functions for dealing with 3D geometry
"""


[docs]def transpose_matrix3(m): ''' Transpose Matrix3 in place ''' t = m[0+3*1] m[0+3*1] = m[1+3*0] m[1+3*0] = t t = m[0+3*2] m[0+3*2] = m[2+3*0] m[2+3*0] = t t = m[1+3*2] m[1+3*2] = m[2+3*1] m[2+3*1] = t
[docs]def plane_rotation(tri, norm=None) -> _nukemath.Vector3: ''' Calculate the rotations around the X, Y and Z axes in radians that will align a plane perpendicular to the Z axis with the given triangle. @param tri: A list or tuple of 3 _nukemath.Vector3 objects. The 3 points must describe the plane (i.e. they must not be collinear). @return: A _nukemath.Vector3 object where the x coordinate is the angle of rotation around the x axis and so on in radians. @raise ValueError: if the three points are collinear. ''' # Get the vectors along two edges of the triangle described by the three points. a = tri[1] - tri[0] b = tri[2] - tri[0] # Calculate the basis vectors for an orthonormal basis, where: # - u is parallel to a # - v is perpendicular to u and lies in the plane defined by a and b # - w is perpendicular to both u and v u = _nukemath.Vector3(a) u.normalize() w = a.cross(b) w.normalize() v = w.cross(u) # If a normal was passed in, check to make sure that the one we're generating # is aligned close to the same way if norm is not None: if w.dot(norm) < 0.0: w.x = -w.x w.y = -w.y w.z = -w.z # Don't mirror! v.x = -v.x v.y = -v.y v.z = -v.z # Now find the rotation angles necessary to align a card to the uv plane. m = ((u[0], v[0], w[0]), (u[1], v[1], w[1]), (u[2], v[2], w[2])) if abs(m[1][2]) == 1.0: ry = 0.0 rx = (math.pi / 2.0) * -m[1][2] rz = math.atan2(-m[0][1], m[0][0]) else: cosx = math.sqrt(m[0][2] ** 2 + m[2][2] ** 2) if cosx == 0: cosx = 1.0 rx = math.atan2(-m[1][2], cosx) rz = math.atan2(m[1][0] / cosx, m[1][1] / cosx) ry = math.atan2(m[0][2] / cosx, m[2][2] / cosx) return _nukemath.Vector3(rx, ry, rz)
[docs]def translate_matrix(translations) -> _nukemath.Matrix4: ''' Generates a translation matrix from the input vector. @type translations: _nukemath.Vector3 @param translations: Vector that will be used generate the translation matrix. @return: The translate matrix. ''' m = _nukemath.Matrix4() m.makeIdentity() m.translation(translations[0], translations[1], translations[2]) return m
[docs]def rotate_matrix_xyz(rotations) -> _nukemath.Matrix4: ''' Generates a rotation XYZ matrix from the input vector. @type rotations: _nukemath.Vector3 @param rotations: Vector that will be used generate the rotate matrix. @return: The rotate matrix. ''' m = _nukemath.Matrix4() m.makeIdentity() m.rotateZ(rotations[2]) m.rotateY(rotations[1]) m.rotateX(rotations[0]) return m
[docs]def scaling_matrix(scalings) -> _nukemath.Matrix4: ''' Generates a scaling matrix from the input vector. @type scalings: _nukemath.Vector3 @param scalings: Vector that will be used generate the scaling matrix. @return: The scaling matrix. ''' m = _nukemath.Matrix4() m.makeIdentity() m.scaling(scalings[0], scalings[1], scalings[2]) return m
[docs]def rotate_matrix_zxy(rotations) -> _nukemath.Matrix4: ''' Generates a rotation ZXY matrix from the input vector. @type rotations: _nukemath.Vector3 @param rotations: Vector that will be used generate the rotate matrix. @return: The rotate matrix. ''' m = _nukemath.Matrix4() m.makeIdentity() m.rotateY(rotations[1]) m.rotateX(rotations[0]) m.rotateZ(rotations[2]) return m
[docs]def get_world_point_on_bbox(prim, position: str, time) -> _nukemath.Vector3: ''' Return points inside or on the bounding box of a prim in world space ''' from pxr import UsdGeom, Gf target_imageable = UsdGeom.Imageable(prim) local_bounds_box = target_imageable.ComputeLocalBound(time, UsdGeom.Tokens.default_, UsdGeom.Tokens.render, UsdGeom.Tokens.proxy) bounds_range = local_bounds_box.GetBox() bounds_range_min = bounds_range.GetMin() bounds_range_size = bounds_range.GetSize() point = None if position == "Center": point = bounds_range_min + Gf.Vec3d(bounds_range_size[0] / 2, bounds_range_size[1] / 2, bounds_range_size[2] / 2) elif position == "Top": point = bounds_range_min + Gf.Vec3d(bounds_range_size[0] / 2, bounds_range_size[1], bounds_range_size[2] / 2) elif position == "Bottom": point = bounds_range_min + Gf.Vec3d(bounds_range_size[0] / 2, 0, bounds_range_size[2] / 2) elif position == "Left": point = bounds_range_min + Gf.Vec3d(0, bounds_range_size[1] / 2, bounds_range_size[2] / 2) elif position == "Right": point = bounds_range_min + Gf.Vec3d(bounds_range_size[0], bounds_range_size[1] / 2, bounds_range_size[2] / 2) elif position == "Front": point = bounds_range_min + Gf.Vec3d(bounds_range_size[0] / 2, bounds_range_size[1] / 2, bounds_range_size[2]) elif position == "Back": point = bounds_range_min + Gf.Vec3d(bounds_range_size[0] / 2, bounds_range_size[1] / 2, 0) else: raise ValueError("Invalid snapping position") local_to_world_transform = target_imageable.ComputeLocalToWorldTransform(time) return _nukemath.Vector3(*local_to_world_transform.Transform(point))
[docs]def get_prim_world_rotation_zxy(prim, time) -> list: ''' Get xyz rotation angles in radians for the prim in world space assuming zxy rotation order ''' from pxr import UsdGeom, Gf xform = UsdGeom.Xformable(prim) mat: Gf.Matrix4d = xform.ComputeLocalToWorldTransform(time) mat.Orthonormalize() rotation_deg = mat.ExtractRotation().Decompose(Gf.Vec3d(0, 1, 0), Gf.Vec3d(1, 0, 0), Gf.Vec3d(0, 0, 1)) return [math.radians(rotation_deg[i]) for i in (1, 0, 2)]