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:
objectRepresent 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:
- 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:
Notes
fis 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:
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:
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:
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:
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:
- Returns:
A list of new
Planeobjects.- Return type:
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
pointonto the plane surface.- Return type:
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:
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:
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
Nonefor 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
Planeobjects.- Return type:
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:
- Raises:
ValueError – If
pointdoes 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:
- Returns:
Rotated circle points of shape
(num_points, 3).- Return type:
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
Planeobjects 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:
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)}")