upxo.geoEntities.plane module

3D plane geometric entity for UPXO.

This module provides a lightweight Plane class with helpers for construction, projection, distance calculations, parallel stacks, and visualisation in 3D.

Classes

Plane

A 3D plane defined by a point and a normal vector.

Usage

from upxo.geoEntities.plane import Plane

@author: Dr. Sunil Anandatheertha

class upxo.geoEntities.plane.Plane(point, normal)[source]

Bases: object

Represent a 3D plane using a point and a normal vector.

classmethod from_three_points(point1, point2, point3)[source]

Construct a plane from three non-collinear points.

Parameters:
  • point1 (array-like) – First point on the plane.

  • point2 (array-like) – Second point on the plane.

  • point3 (array-like) – Third point on the plane.

Returns:

Plane instance passing through the three points.

Return type:

Plane

Raises:

ValueError – If any input point is invalid, if any two input points coincide, or if the three points are collinear.

Notes

The normal is computed as cross(point2 - point1, point3 - point1). Input points must not be collinear.

Examples

from upxo.geoEntities.plane import Plane
import numpy as np

point1 = np.array([1, 0, 2])
point2 = np.array([0, 2, 1])
point3 = np.array([3, 1, -1])
plane = Plane.from_three_points(point1, point2, point3)
print(plane)
classmethod from_edge(point1, point2, f=0.5)[source]

Create a plane normal to an edge and passing through a point on it.

Parameters:
  • point1 (array-like) – Start point of the edge.

  • point2 (array-like) – End point of the edge.

  • f (float, optional) – Fraction along the edge in [0, 1] where the plane passes.

Returns:

Plane with normal parallel to point2 - point1.

Return type:

Plane

Notes

f is clipped into [0, 1] before use.

Examples

from upxo.geoEntities.plane import Plane
import numpy as np

point1 = np.array([1, 0, 2])
point2 = np.array([3, 2, 0])

midpoint_plane = Plane.from_edge(point1, point2)
three_quarter_plane = Plane.from_edge(point1, point2, f=0.75)
classmethod from_euler_angles(euler_angles, point_on_plane, ea_format='rpy', degree=False)[source]

Construct plane from Euler-angle orientation and a point.

Parameters:
  • euler_angles (tuple or list) – Euler angles corresponding to ea_format.

  • point_on_plane (tuple or list) – Coordinates (x, y, z) of a point on the plane.

  • ea_format ({'rpy', 'bunge', 'roe'}, optional) – Convention used for euler_angles.

  • degree (bool, optional) – If True, interpret Euler angles in degrees.

Returns:

Plane instance whose normal is derived from Euler rotations.

Return type:

Plane

Notes

The method rotates the reference normal [0, 0, 1] using the convention-specific rotation mapping.

Examples

from upxo.geoEntities.plane import Plane
import numpy as np

Plane.from_euler_angles((0, np.pi/2, np.pi/2), (1, 1, 1), ea_format='rpy', degree=False)
Plane.from_euler_angles((0, np.pi/2, np.pi/2), (1, 1, 1), ea_format='roe', degree=False)
Plane.from_euler_angles((0, np.pi/2, np.pi/2), (1, 1, 1), ea_format='bunge', degree=False)
Plane.from_euler_angles((0, np.pi/7, np.pi/2), (1, 1, 1), ea_format='bunge', degree=False)
property unit_normal

Return the unit normal vector of the plane.

Returns:

Unit vector in the direction of the plane normal.

Return type:

numpy.ndarray

Examples

from upxo.geoEntities.plane import Plane

Plane(point=(1, 1, 1), normal=(2, -1, 3)).unit_normal
distance_to_point(point)[source]

Calculate signed distance from the plane to a point.

Positive when the point is on the same side as the normal, negative otherwise.

Parameters:

point (array-like) – 3D point coordinates.

Returns:

Signed perpendicular distance from the plane to point.

Return type:

float

Examples

from upxo.geoEntities.plane import Plane
import numpy as np

plane = Plane(point=(0, 0, 0), normal=(0, 0, 1))
plane.distance_to_point(np.array([1, 2, 3]))  # returns 3.0
calc_perp_distances(points, signed=True)[source]

Compute perpendicular distances from plane to one or more points.

Parameters:
  • points (array-like) – Single 3D point or array of shape (n, 3).

  • signed (bool, optional) – If True, return signed distances. If False, return absolute values.

Returns:

Distance(s) from the plane to the input point set.

Return type:

numpy.ndarray

Examples

from upxo.geoEntities.plane import Plane
import numpy as np

plane = Plane(point=(1, 1, 0), normal=(1, 1, 2))
points = [np.array([0, 2, 1]), np.array([3, 0, -1]),
          np.array([2, 2, 2])]
plane.calc_perp_distances(points)
find_close_points(point_coords, cod=0.25)[source]

Find points within a cutoff distance from the plane.

Parameters:
  • point_coords (array-like) – Candidate points of shape (n, 3).

  • cod (float, optional) – Cutoff distance threshold.

Returns:

Current implementation computes distances but does not yet return selected points.

Return type:

None

Notes

To be developed.

Examples

from upxo.geoEntities.plane import Plane
from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
import numpy as np

plane = Plane(point=(0.5, 0.5, 0.5), normal=(1, 1, 1))
xspec, yspec, zspec = [0, 1, 0.2], [0, 1, 0.2], [0, 1, 0.2]
mulpoint3d = mp3d.from_xyz_grid(
    xspec=xspec, yspec=yspec, zspec=zspec,
    dxyz=[0.0, 0.0, 0.0], translate_ref=[0.5, 0.5, 0.5],
    rot=[0.0, 0.0, 0.0], rot_ref=[0.5, 0.5, 0.5], degree=True)
D = plane.calc_perp_distances(mulpoint3d.coords, signed=False)
cod = 0.1
coords_within_cod = mulpoint3d.coords[np.argwhere(D <= cod)].squeeze()
mulpoint3d.plot(coords_within_cod, primary_ms=25, primary_alpha=0.0,
        secondary_alpha=0.25, xbound=xspec[:2], ybound=yspec[:2],
        zbound=zspec[:2])
create_parallel_stack(spacing, num_planes)[source]

Create a stack of planes parallel to this one.

Parameters:
  • spacing (float) – The perpendicular distance between each parallel plane.

  • num_planes (int) – The total number of planes to generate in the stack.

Returns:

A list of new Plane objects.

Return type:

list

Examples

from upxo.geoEntities.plane import Plane

base = Plane(point=(0, 0, 0), normal=(0, 0, 1))
stack = base.create_parallel_stack(spacing=0.5, num_planes=5)
for p in stack:
    print(p)
project_point(point)[source]

Project a point onto the plane.

Parameters:

point (array-like) – 3D point to project.

Returns:

Projection of point onto the plane surface.

Return type:

numpy.ndarray

Notes

The signed distance from the plane to the point is computed first (via distance_to_point()), then the point is translated along the normal by that distance to reach its closest position on the plane.

Examples

from upxo.geoEntities.plane import Plane

Plane(point=(1, 1, 1), normal=(1, 1, 1)).project_point((0, 0, 0))
Plane(point=(1, 1, 1), normal=(-1, -1, -1)).project_point((0, 0, 0))
generate_random_points(num_points=3)[source]

Generate random points lying on the plane.

Parameters:

num_points (int, optional) – Number of points to generate.

Returns:

Random points represented as coordinate tuples.

Return type:

list

Notes

Two in-plane orthogonal directions are constructed from the normal, then random linear combinations are sampled.

Examples

from upxo.geoEntities.plane import Plane

plane = Plane(point=(0, 0, 0), normal=(0, 0, 1))
plane.generate_random_points(3)
flip_normal()[source]

Flip the direction of the plane normal.

Notes

Flipping the normal changes the side of the plane that is considered the “front.” Be mindful of how this affects signed distance calculations and geometric operations that depend on normal orientation.

To be developed.

is_parallel(other_plane)[source]

Check whether this plane is parallel to another plane.

Parameters:

other_plane (Plane) – Plane to compare against.

Returns:

True if normals are parallel (or anti-parallel), else False.

Return type:

bool

Examples

from upxo.geoEntities.plane import Plane

plane1 = Plane(point=(1, 1, 1), normal=(+2, -1, +3))
plane2 = Plane(point=(0, 0, 0), normal=(+2, -1, +3))
plane3 = Plane(point=(1, 1, 1), normal=(-2, +1, -3))
plane4 = Plane(point=(1, 1, 1), normal=(-2, -1, -3))

plane1.is_parallel(plane1)  # True
plane1.is_parallel(plane2)  # True
plane1.is_parallel(plane3)  # True (anti-parallel normals)
plane1.is_parallel(plane4)  # False
find_intersection_vector(plane)[source]

Find direction vector of intersection line between two planes.

Parameters:

plane (Plane) – Second plane.

Returns:

Direction vector of line of intersection, or None for parallel planes.

Return type:

numpy.ndarray or None

Notes

This method returns direction only; computing a point on the intersection line requires additional constraints.

Examples

from upxo.geoEntities.plane import Plane

plane1 = Plane(point=(1, 1, 0), normal=(1, 1, 2))
plane2 = Plane(point=(0, 2, 1), normal=(-2, -1, 1))
intersection_vector = plane1.find_intersection_vector(plane2)
print(intersection_vector)
create_translated_planes(translation_vector, num_planes, dlk=numpy.array, dnw=numpy.array, dno=numpy.array, bidrectional=False)[source]

Create translated copies of the current plane.

Parameters:
  • translation_vector (array-like) – Base translation increment applied between planes.

  • num_planes (int) – Number of planes to generate (including self in one-sided mode).

  • dlk (numpy.ndarray, optional) – Random translational perturbation scale.

  • dnw (numpy.ndarray, optional) – Random normal perturbation scale.

  • dno (numpy.ndarray, optional) – Offset term used with normal perturbation.

  • bidrectional (bool, optional) – If True, generate planes on both positive and negative directions.

Returns:

Array of Plane objects.

Return type:

numpy.ndarray

Examples

Example 1 — simple translated stack:

from upxo.geoEntities.plane import Plane
import numpy as np

plane = Plane(point=(0, 0, 0), normal=(1, 1, 1))
translated = plane.create_translated_planes(
    np.array([2, 3, -1]), num_planes=4)
print(translated)

Example 2 — translated planes with point filtering and plotting:

from upxo.geoEntities.plane import Plane
from upxo.geoEntities.mulpoint3d import MPoint3d as mp3d
import numpy as np
import matplotlib.pyplot as plt

xspec, yspec, zspec = [0, 1, 0.05], [0, 1, 0.05], [0, 1, 0.05]
mpnt3d = mp3d.from_xyz_grid(
    xspec=xspec, yspec=yspec, zspec=zspec,
    dxyz=[0.0, 0.0, 0.0], translate_ref=[0.5, 0.5, 0.5],
    rot=[0.0, 0.0, 0.0], rot_ref=[0.5, 0.5, 0.5], degree=True)
plane = Plane(point=(0.0, 0.0, 0.0), normal=(1, 1, 1))
planes = plane.create_translated_planes(np.array([0.2, 0.2, 0.2]),
                                        num_planes=8)
D = [p.calc_perp_distances(mpnt3d.coords, signed=False) for p in planes]
cod = 0.125
coords = [mpnt3d.coords[np.argwhere(d <= cod)].squeeze() for d in D]

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(mpnt3d.coords[:, 0], mpnt3d.coords[:, 1],
           mpnt3d.coords[:, 2], c='b', marker='o', alpha=0.01,
           s=100, edgecolors='black')
for coord in coords:
    if coord is not None:
        ax.scatter(coord[:, 0], coord[:, 1], coord[:, 2],
                   c=np.random.random(3), marker='o', alpha=0.8,
                   s=50, edgecolors='black')
ax.set_xlabel('X'); ax.set_ylabel('Y'); ax.set_zlabel('Z')
plt.show()

Example 3 — with perturbation parameters:

from upxo.geoEntities.plane import Plane
import numpy as np

plane1 = Plane(point=(0, 0, 0), normal=(0, 1, 2))
planes = plane1.create_translated_planes(
    np.array([1, -1, 1]), num_planes=10,
    dlk=np.array([0.0, 0.0, 0.0]),
    dnw=np.array([0.1, 0.0001, 0.0]),
    dno=np.array([0.5, 0.5, 0.5]))
print(planes)
offset_point_on_plane(point, offset_vector)[source]

Offset a point on the plane by an in-plane displacement vector.

Parameters:
  • point (array-like) – Point constrained to lie on the plane.

  • offset_vector (array-like) – Desired offset vector (possibly containing out-of-plane component).

Returns:

New point after applying in-plane component of offset.

Return type:

numpy.ndarray

Raises:

ValueError – If point does not lie on the plane.

Examples

from upxo.geoEntities.plane import Plane
import numpy as np

my_plane = Plane(point=(2, -1, 0), normal=(0, 1, 1))
point_on_plane = np.array([2, -1, 0])
offset_vector = np.array([1, -1, 2])
offset_point = my_plane.offset_point_on_plane(point_on_plane, offset_vector)
print(offset_point)
calculate_inclined_circle(center, radius, angle_A, angle_B, angle_C)[source]

Compute points of an inclined circle associated with the plane.

Parameters:
  • center (array-like) – Circle center.

  • radius (float) – Circle radius.

  • angle_A (float) – Inclination rotation about x-axis (radians).

  • angle_B (float) – Inclination rotation about y-axis (radians).

  • angle_C (float) – Inclination rotation about z-axis (radians).

Returns:

Rotated circle points of shape (num_points, 3).

Return type:

numpy.ndarray

Examples

from upxo.geoEntities.plane import Plane
import numpy as np

my_plane = Plane(point=(1, 0, 2), normal=(0, 1, -1))
center = np.array([1, 2, 2])
radius = 1.5
angle_A = np.radians(30)
angle_B = np.radians(-20)
angle_C = np.radians(60)
circle_points = my_plane.calculate_inclined_circle(
    center, radius, angle_A, angle_B, angle_C)
my_plane.visualize(circle_points)
visualize(circle_points=None)[source]

Visualize plane and optional circle in a 3D matplotlib figure.

Parameters:

circle_points (array-like, optional) – Optional (n, 3) points to overlay on the plane.

Return type:

None

visualize1(circle_points=None, other_planes=None)[source]

Visualize current plane, optional circle, and optional extra planes.

Parameters:
  • circle_points (array-like, optional) – Optional (n, 3) circle points to plot.

  • other_planes (list, optional) – Additional Plane objects to render.

Return type:

None

Examples

from upxo.geoEntities.plane import Plane
import numpy as np

plane1 = Plane(point=(1, 0, 2), normal=(0, 1, -1))
plane2 = Plane(point=(-1, 1, 0), normal=(1, 1, 1))
circle_points = plane1.calculate_inclined_circle(
    center=[1, 2, 2], radius=1.5,
    angle_A=np.radians(30), angle_B=np.radians(-20),
    angle_C=np.radians(60))
plane1.visualize1(circle_points, other_planes=[plane2])
angle_between_planes(other_plane)[source]

Calculate angle between this plane and another plane.

Parameters:

other_plane (Plane) – Plane to compare.

Returns:

Angle between plane normals in radians.

Return type:

float

Examples

from upxo.geoEntities.plane import Plane
import numpy as np

plane1 = Plane(point=(0, 0, 0), normal=(1, 1, 0))
plane2 = Plane(point=(0, 0, 0), normal=(1, 0, 0))
intersection_angle = plane1.angle_between_planes(plane2)
print(f"Angle (radians): {intersection_angle}")
print(f"Angle (degrees): {np.degrees(intersection_angle)}")