"""
3D straight line geometric entity module for UPXO.
Provides two 3-D straight-line classes — a lean variant for minimal memory
footprint and a full-featured class for geometric operations.
Applications
------------
- Non-conformal to conformal geometry conversion.
- Hierarchical grain structure feature generation.
- General 3-D geometry operations.
Classes
-------
Sline3d_leanest
Minimal 3-D line stored as six scalar endpoint coordinates.
Sline3d
Full-featured 3-D line with geometric properties and spatial operations.
Coordinate system
-----------------
::
Y+
| Z-
| /
| /
| /
| /
X- | / X+
-----------------O------------------
/|
/ |
/ |
/ |
/ |
/ |
Z+ Y-
Usage
-----
::
from upxo.geoEntities.sline3d import Sline3d as sl3d
from upxo.geoEntities.sline3d import Sline3d_leanest as sl3dl
@author: Dr. Sunil Anandatheertha
"""
import math
import numpy as np
from copy import deepcopy
from scipy.spatial import cKDTree
import vtk
from shapely.geometry import Point as ShPnt, Polygon as ShPol
from functools import wraps
import matplotlib.pyplot as plt
import upxo._sup.dataTypeHandlers as dth
from upxo.geoEntities.bases import UPXO_Point, UPXO_Edge
np.seterr(divide='ignore')
from upxo._sup.validation_values import isinstance_many
from upxo.geoEntities.point3d import Point3d
[docs]
class Sline3d_leanest():
"""Lean 3-D straight line storing only six scalar endpoint coordinates.
Provides a minimal footprint for high-frequency operations where only
coordinate access, length, and iteration are required.
Usage
-----
::
from upxo.geoEntities.sline3d import Sline3d_leanest as sl3dl
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d_leanest as sl3dl
e = sl3dl(-2, 3, 4, 5, 1, 2)
for coord in e:
print(coord)
print(e[1])
"""
__slots__ = ('x0', 'y0', 'z0', 'x1', 'y1', 'z1')
def __init__(self, x0=0, y0=0, z0=0, x1=1, y1=0, z1=0):
"""Initialise with six scalar endpoint coordinates."""
self.x0, self.y0, self.z0 = x0, y0, z0
self.x1, self.y1, self.z1 = x1, y1, z1
def __repr__(self):
"""Return a compact ``UPXO-sl3d-lean (x0,y0,z0)-(x1,y1,z1)`` string."""
return f'UPXO-sl3d-lean ({self.x0},{self.y0},{self.z0})-({self.x1},{self.y1},{self.z1})'
def __iter__(self):
"""Iterate over the two endpoint coordinate tuples."""
return (i for i in ((self.x0, self.y0, self.z0),
(self.x1, self.y1, self.z1)))
def __getitem__(self, index):
"""Index the line: 0 → start point, 1 → end point."""
return ((self.x0, self.y0, self.z0),
(self.x1, self.y1, self.z1))[index]
@property
def length(self):
"""Euclidean length of the line segment."""
return math.sqrt((self.x1-self.x0)**2+(self.y1-self.y0)**2+(self.z1-self.z0)**2)
[docs]
class Sline3d():
"""Full-featured 3-D straight line entity for UPXO geometric operations.
Stores a straight line segment by its two endpoint coordinates
``(x0, y0, z0)`` and ``(x1, y1, z1)``, and exposes a rich API for
geometric queries (length, midpoint, direction cosines, angles),
distance calculations, splitting, and iterative extension.
Usage
-----
::
from upxo.geoEntities.sline3d import Sline3d as sl3d
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
a = sl3d(0, 0, 0, 1, 1, 1)
print(a.length, a.mid)
"""
__slots__ = ('x0', 'y0', 'z0', 'x1', 'y1', 'z1', 'f', 'pnta', 'pntb')
def __init__(self, x0=0, y0=0, z0=0, x1=1, y1=0, z1=0, pnta=None, pntb=None):
"""Initialise from six scalar coordinates or two ``Point3d`` endpoints.
Parameters
----------
x0, y0, z0 : float, optional
Start-point coordinates. Used when ``pnta`` is ``None``.
Default is (0, 0, 0).
x1, y1, z1 : float, optional
End-point coordinates. Used when ``pntb`` is ``None``.
Default is (1, 0, 0).
pnta : Point3d, optional
Start ``Point3d`` object. When provided, ``x0/y0/z0`` are
derived from it.
pntb : Point3d, optional
End ``Point3d`` object. When provided, ``x1/y1/z1`` are
derived from it.
"""
if pnta is None and pntb is None:
self.x0, self.y0, self.z0 = x0, y0, z0
self.x1, self.y1, self.z1 = x1, y1, z1
self.pnta, self.pntb = Point3d(x0, y0, z0), Point3d(x1, y1, z1)
if pnta is not None and pntb is not None:
self.pnta, self.pntb = pnta, pntb
self.x0, self.y0, self.z0 = pnta.x, pnta.y, pnta.z
self.x1, self.y1, self.z1 = pntb.x, pntb.y, pnta.z
def __repr__(self):
"""Return ``UPXO-sl3d (x0,y0,z0)-(x1,y1,z1). <id>`` summary string."""
return f'UPXO-sl3d ({self.x0},{self.y0},{self.z0})-({self.x1},{self.y1},{self.z1}). {id(self)}'
def __iter__(self):
"""Iterate over the two endpoint coordinate tuples."""
return (i for i in ((self.x0, self.y0, self.z0),
(self.x1, self.y1, self.z1)))
def __getitem__(self, index):
"""Index the line: 0 → start point, 1 → end point."""
return ((self.x0, self.y0, self.z0),
(self.x1, self.y1, self.z1))[index]
def __eq__(self, lines):
"""Return per-line equality with ``self`` based on length comparison.
Parameters
----------
lines : iterable of Sline3d
Lines to compare against ``self``.
Returns
-------
list of bool
``True`` where the other line has the same length as ``self``.
"""
length = self.length
return [length == e.length for e in lines]
def __ne__(self, lines):
"""Return per-line inequality with ``self``."""
return [not eeq for eeq in self == lines]
def __lt__(self, lines):
"""Return per-line ``self.length < other.length`` comparisons."""
length = self.length
return [length < l for l in lines]
[docs]
@classmethod
def by_coord(cls, start, end):
"""Construct a ``Sline3d`` from two ``[x, y, z]`` coordinate lists.
Parameters
----------
start : list of float, length 3
Start-point coordinates ``[x0, y0, z0]``.
end : list of float, length 3
End-point coordinates ``[x1, y1, z1]``.
Returns
-------
Sline3d
New line from ``start`` to ``end``.
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
A = sl3d.by_coord([-1, 2, 0], [3, 4, 1])
"""
return cls(start[0], start[1], start[2], end[0], end[1], end[2])
[docs]
@classmethod
def by_p3d(cls, start, end):
"""Construct a ``Sline3d`` from two ``Point3d`` endpoint objects.
Parameters
----------
start : Point3d
Start-point UPXO point object.
end : Point3d
End-point UPXO point object.
Returns
-------
Sline3d
New line from ``start`` to ``end``.
Examples
--------
.. code-block:: python
from upxo.geoEntities.point3d import Point3d
from upxo.geoEntities.sline3d import Sline3d
Sline3d.by_p3d(Point3d(-1, 2, 3), Point3d(3, 4, 1))
"""
return cls(pnta=start, pntb=end)
[docs]
@classmethod
def by_vector(cls, point, xyproj):
"""Construct from an endpoint and a direction vector. Not yet implemented."""
raise NotImplementedError("by_vector is not yet implemented.")
@property
def mid(self):
"""Midpoint of the line as ``(xmid, ymid, zmid)`` tuple.
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
sl3d(0, 0, 0, 1, 1, 1).mid
"""
return (self.xmid, self.ymid, self.zmid)
@property
def xmid(self):
"""x-coordinate of the midpoint.
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
sl3d(0, 0, 0, 1, 1, 1).xmid
"""
return (self.x0 + self.x1)/2
@property
def ymid(self):
"""y-coordinate of the midpoint.
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
sl3d(0, 0, 0, 1, 1, 1).ymid
"""
return (self.y0 + self.y1)/2
@property
def zmid(self):
"""z-coordinate of the midpoint.
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
sl3d(0, 0, 0, 1, 1, 1).zmid
"""
return (self.z0 + self.z1)/2
@property
def gradient(self):
"""Gradient of the line as ``[dx/dz, dy/dz]``.
Returns ``math.inf`` in the corresponding component when the line is
horizontal (``z0 == z1``).
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
sl3d(0, 0, 0, 1, 1, 1).gradient
"""
grad = [math.inf, math.inf]
dx, dy, dz = self.delxyz
if self.z0 != self.z1:
grad[0] = (dx)/(dz)
if self.z0 != self.z1:
grad[1] = (dy)/(dz)
return grad
@property
def delxyz(self):
"""Length increments ``(dx, dy, dz)`` along each axis.
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
sl3d(0, 0, 0, 1, 1, 1).delxyz
"""
return self.dx, self.dy, self.dz
@property
def dx(self):
"""Signed length increment along x (``x1 - x0``).
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
sl3d(0, 0, 0, 1, 1, 1).dx
"""
return self.x1-self.x0
@property
def dy(self):
"""Signed length increment along y (``y1 - y0``).
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
sl3d(0, 0, 0, 1, 1, 1).dy
"""
return self.y1-self.y0
@property
def dz(self):
"""Signed length increment along z (``z1 - z0``).
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
sl3d(0, 0, 0, 1, 1, 1).dz
"""
return self.z1-self.z0
@property
def ang(self):
"""Orientation angles in radians as ``[angle_x, angle_y, angle_z]``.
Computed as:
``angle_x = atan2(dy, dz)``,
``angle_y = atan2(dx, dz)``,
``angle_z = atan2(dy, dx)``.
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
sl3d(0, 0, 0, 1, 1, 1).ang
"""
dx, dy, dz = self.delxyz
angle_x = math.atan2(dy, dz)
angle_y = math.atan2(dx, dz)
angle_z = math.atan2(dy, dx)
return [angle_x, angle_y, angle_z]
@property
def angd(self):
"""Orientation angles in degrees as ``[angle_x, angle_y, angle_z]``.
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
sl3d(0, 0, 0, 1, 1, 1).angd
"""
return [math.degrees(ang) for ang in self.ang]
@property
def length(self):
"""Euclidean length of the line segment.
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
sl3d(0, 0, 0, 1, 1, 1).length
"""
return math.sqrt((self.x1-self.x0)**2+(self.y1-self.y0)**2+(self.z1-self.z0)**2)
@property
def dc(self):
"""Direction cosines ``(dx/L, dy/L, dz/L)`` where L is the length.
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
sl3d(0, 0, 0, 1, 1, 1).dc
"""
dx, dy, dz = self.delxyz
length = self.length
return dx/length, dy/length, dz/length
@property
def coords(self):
"""Flat coordinate list ``[x0, y0, z0, x1, y1, z1]``.
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
sl3d(0, 0, 0, 1, 1, 1).coords
"""
return [self.x0, self.y0, self.z0, self.x1, self.y1, self.z1]
@property
def coord_list(self):
"""Endpoint coordinates as ``[[x0, y0, z0], [x1, y1, z1]]``.
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
sl3d(0, 0, 0, 1, 1, 1).coord_list
"""
return [[self.x0, self.y0, self.z0], [self.x1, self.y1, self.z1]]
@property
def coord_i(self):
"""Start coordinate as ``[x0, y0, z0]``."""
return [self.x0, self.y0, self.z0]
@property
def coord_j(self):
"""End coordinate as ``[x1, y1, z1]``."""
return [self.x1, self.y1, self.z1]
@property
def points(self):
"""Return ``[Point3d(i), Point3d(mid), Point3d(j)]``."""
from upxo.geoEntities.point3d import Point3d
mp = self.mid
return [Point3d(self.x0, self.y0, self.z0),
Point3d(mp[0], mp[1], mp[2]),
Point3d(self.x1, self.y1, self.z1)]
[docs]
def is_point_endpoint(self, point):
"""Return ``True`` if ``point`` coincides with either endpoint.
Parameters
----------
point : array-like, length 3
Candidate ``[x, y, z]`` coordinate.
Returns
-------
bool
``True`` if ``point`` equals ``(x0, y0, z0)`` or
``(x1, y1, z1)``; ``False`` otherwise.
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
line = sl3d(0, 0, 0, 1, 1, 1)
print(line.is_point_endpoint((0, 0, 0))) # True
print(line.is_point_endpoint([1, 2, 3])) # False
"""
is_endpoint = False
if np.any(np.all(np.array(self.coord_list) == point, axis=1)):
is_endpoint = True
return is_endpoint
[docs]
def invert(self):
"""Swap start and end endpoints in place."""
ends = self.coord_list
self.x0, self.y0, self.z0 = ends[1]
self.x1, self.y1, self.z1 = ends[0]
[docs]
def move_i(self, point):
"""Move the start point to ``point`` in place."""
self.x0, self.y0, self.z0 = point
[docs]
def move_j(self, point):
"""Move the end point to ``point`` in place."""
self.x1, self.y1, self.z1 = point
[docs]
def distance_to_points(self, points=None, *, ref='all'):
"""Calculate the Euclidean distance from a reference location on the line to a set of points.
Parameters
----------
points : list of Point3d
Target points to measure distance to.
ref : {'all', 'i', 'start', 'mid', 'j', 'end'}, optional
Reference location on the line:
* ``'all'`` — compute from start, mid, and end (returns list of 3 arrays).
* ``'i'`` / ``'start'`` — from the start endpoint.
* ``'mid'`` — from the midpoint.
* ``'j'`` / ``'end'`` — from the end endpoint.
Default is ``'all'``.
Returns
-------
list or numpy.ndarray
When ``ref='all'``, a list of three distance arrays
``[dist_from_i, dist_from_mid, dist_from_j]``; otherwise a
single distance array.
Examples
--------
**Example 1** — distances from all three reference locations:
.. code-block:: python
from upxo.geoEntities.point3d import Point3d as p3d
from upxo.geoEntities.sline3d import Sline3d as sl3d
import numpy as np
line = sl3d(0, 0, 0, 1, 1, 1)
points = [p3d(xy[0], xy[1], xy[2]) for xy in np.random.random((10, 3))]
line.distance_to_points(points, ref='all')
line.distance_to_points(points, ref='i')
line.distance_to_points(points, ref='mid')
line.distance_to_points(points, ref='j')
"""
if ref == 'all':
pnti, pntmid, pntj = self.points
distances = [pnti.distance(points),
pntmid.distance(points),
pntj.distance(points)]
elif ref in ('i', 'start'):
distances = self.points[0].distance(points)
elif ref in ('mid'):
distances = self.points[1].distance(points)
elif ref in ('j', 'end'):
distances = self.points[2].distance(points)
return distances
[docs]
def perp_distance(self, point, ptype='coord_list'):
"""Compute the perpendicular distance from a point to the infinite line.
Parameters
----------
point : array-like or list of Point3d
Target point(s). Interpretation depends on ``ptype``.
ptype : {'coord_list', '[point3d]', 'point3d', 'p3d'}, optional
Specifies the format of ``point``. Default is ``'coord_list'``.
Returns
-------
float
Perpendicular distance from ``point`` to the line.
Examples
--------
**Example 1** — single coordinate triple:
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
line = sl3d(0, 0, 0, 1, 0, 0)
line.perp_distance((1, 1, 1))
**Example 2** — vectorised version for many points (see :meth:`perp_distance_vectorized`):
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
import numpy as np
line = sl3d(0, 0, 0, 1, 0, 0)
plist = np.random.random((10000, 3))
line.perp_distance_vectorized(plist, ptype='coord_list')
"""
if ptype == 'coord_list':
x, y, z = np.array(point)
elif ptype in ('[point3d]', 'point3d', 'p3d'):
if type(point) not in dth.dt.ITERABLES:
plist = [point]
x, y, z = np.array([[p.x, p.y, p.z] for p in plist]).T
point = np.array([x, y, z])
line_start = np.array(self.coord_list[0])
line_end = np.array(self.coord_list[1])
line_vec = line_end - line_start
point_vec = point - line_start
projection = np.dot(point_vec,
line_vec) / np.dot(line_vec, line_vec) * line_vec
perpendicular_vec = point_vec - projection
distance = np.linalg.norm(perpendicular_vec)
return distance
[docs]
def perp_distance_vectorized(self, points, ptype='coord_list'):
"""Compute perpendicular distances from many points to the infinite line (vectorised).
Parameters
----------
points : numpy.ndarray, shape (M, 3), or list of Point3d
Target points.
ptype : {'coord_list', '[point3d]', 'point3d', 'p3d'}, optional
Format of ``points``. Default is ``'coord_list'``.
Returns
-------
numpy.ndarray, shape (M,)
Perpendicular distance from each point to the line.
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
import numpy as np
line = sl3d(0, 0, 0, 0.5, 0.5, 0.5)
x = np.linspace(0, 1, 4)
points = np.stack(np.meshgrid(x, x, x), axis=-1).reshape(-1, 3)
line.perp_distance_vectorized(points, ptype='coord_list')
"""
if ptype == 'coord_list':
points = np.array(points)
elif ptype in ('[point3d]', 'point3d', 'p3d'):
points = np.array([[p.x, p.y, p.z] for p in points])
line_start = np.array(self.coord_list[0])
line_end = np.array(self.coord_list[1])
line_vec = line_end - line_start
point_vec = points - line_start
line_vec_norm_sq = np.dot(line_vec, line_vec)
projection = (np.dot(point_vec, line_vec)[:, None] / line_vec_norm_sq) * line_vec
perpendicular_vec = point_vec - projection
distances = np.linalg.norm(perpendicular_vec, axis=1)
return distances
[docs]
def extend(self, dincr, direction='both', saa=True, throw=False):
"""Extend the line by ``dincr`` at the start, end, or both ends.
Parameters
----------
dincr : float
Extension increment (in the same units as the line coordinates).
direction : {'both', 'start', 'end'}, optional
Which end(s) to extend. Default is ``'both'``.
saa : bool, optional
If ``True``, update ``self`` in place. Default is ``True``.
throw : bool, optional
If ``True``, return the new endpoint arrays. Default is ``False``.
Returns
-------
tuple of numpy.ndarray or None
When ``throw=True``, returns ``(P1_extended, P2_extended)``;
otherwise ``None``.
"""
P1 = np.array([self.x0, self.y0, self.z0])
P2 = np.array([self.x1, self.y1, self.z1])
line_vec = P2 - P1
unit_vec = line_vec / np.linalg.norm(line_vec)
if direction == 'start':
P1_extended = P1 - dincr * unit_vec
P2_extended = P2
elif direction == 'end':
P1_extended = P1
P2_extended = P2 + dincr * unit_vec
elif direction == 'both':
P1_extended = P1 - dincr * unit_vec
P2_extended = P2 + dincr * unit_vec
else:
raise ValueError("Invalid direction. Choose 'start', 'end', or 'both'.")
if saa:
self.x0, self.y0, self.z0 = P1_extended
self.x1, self.y1, self.z1 = P2_extended
if throw:
return P1_extended, P2_extended
[docs]
def extend_until_exhaustion(self,
points,
voxel_size=1.0,
dincr_factor=0.1,
direction='end',
max_iterations=1000,
saa_final=True,
throw_final=False):
"""Iteratively extend the line until no new points fall within the threshold distance.
At each iteration the line is extended by ``dincr_factor * length`` in
the requested direction. Iteration stops when the count of nearby
points ceases to increase.
Parameters
----------
points : array-like, shape (M, 3)
3-D point cloud the line is extended towards.
voxel_size : float, optional
Voxel edge length; the proximity threshold is
``sqrt(3) * voxel_size`` (body diagonal). Default is 1.0.
dincr_factor : float, optional
Extension per iteration as a fraction of the current line length.
Default is 0.1.
direction : {'end', 'start', 'both'}, optional
Which end(s) to extend. Default is ``'end'``.
max_iterations : int, optional
Safety cap on the number of iterations. Default is 1000.
saa_final : bool, optional
If ``True``, set ``self`` endpoints to the final detected extent.
Default is ``True``.
throw_final : bool, optional
If ``True``, return the final endpoint coordinates.
Default is ``False``.
Returns
-------
tuple of numpy.ndarray or None
When ``throw_final=True`` and ``direction='end'``, returns
``(start_point, actual_end_point)``; for ``direction='both'``,
returns ``(actual_start_point, actual_end_point)``.
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
import numpy as np
LINE = sl3d(0, 0, 0, 0.5, 0.5, 0.5)
x = np.linspace(0, 5, 4)
points = np.stack(np.meshgrid(x, x, x), axis=-1).reshape(-1, 3)
LINE.extend_until_exhaustion(points,
voxel_size=x[1]-x[0],
dincr_factor=0.1,
direction='end')
print(LINE)
"""
threshold_distance = 1.7320508075688772 * voxel_size
P1 = np.array([self.x0, self.y0, self.z0])
P2 = np.array([self.x1, self.y1, self.z1])
points = np.array(points)
pdist = self.perp_distance_vectorized(points, ptype='coord_list')
initial_count = np.sum(pdist <= threshold_distance)
prev_count = initial_count
iteration = 0
while iteration < max_iterations:
iteration += 1
P1_extended, P2_extended = self.extend(dincr_factor*self.length,
direction=direction,
saa=False, throw=True)
_line_ = Sline3d(P1_extended[0], P1_extended[1], P1_extended[2],
P2_extended[0], P2_extended[1], P2_extended[2])
self.x0, self.y0, self.z0 = P1_extended
self.x1, self.y1, self.z1 = P2_extended
pdist = _line_.perp_distance_vectorized(points, ptype='coord_list')
new_count = np.sum(pdist <= threshold_distance)
if new_count <= prev_count:
close_pooints = np.argwhere(pdist <= threshold_distance)
if direction == 'both':
dist_i = self.distance_to_points(points, ref='start')
dist_j = self.distance_to_points(points, ref='end')
far_pnt_i, far_pnt_j = np.argmin(dist_i), np.argmin(dist_j)
actual_start_point = points[far_pnt_i]
actual_end_point = points[far_pnt_j]
if saa_final:
self.x0, self.y0, self.z0 = actual_start_point.tolist()
self.x1, self.y1, self.z1 = actual_end_point.tolist()
if throw_final:
print('The start and end point indices are:')
return actual_start_point, actual_end_point
elif direction == 'start':
pass
elif direction == 'end':
far_pnt = np.argmax(_line_.distance_to_points(points,
ref='end'))
actual_end_point = points[far_pnt]
print(f'No. of iterations: {iteration}')
if saa_final:
self.x1, self.y1, self.z1 = actual_end_point
if throw_final:
print('The start and end point indices are:')
start_point = np.array([self.x1, self.y1, self.z1])
return start_point, actual_end_point
break
P1, P2 = P1_extended, P2_extended
prev_count = new_count
[docs]
def split(self, f=0.5, saa=False, throw=True, retain=0):
"""Split the line at one or more fractional positions.
Parameters
----------
f : float or iterable of float
Fractional position(s) along the line in the open interval (0, 1).
When a scalar, the line is split into two segments. Iterable
support is reserved for future implementation.
saa : bool, optional
If ``True``, shorten ``self`` to retain the segment specified by
``retain`` and discard the other. Default is ``False``.
throw : bool, optional
If ``True``, return the two new ``Sline3d`` objects.
Default is ``True``.
retain : {0, 1}, optional
When ``saa=True``, which half to keep: 0 = start half,
1 = end half. Default is 0.
Returns
-------
list of Sline3d or None
When ``throw=True`` and ``saa=False``, returns
``[line_start_half, line_end_half]``; otherwise ``None``.
Examples
--------
.. code-block:: python
from upxo.geoEntities.sline3d import Sline3d as sl3d
line = sl3d(0, 0, 0, 1, 0, 0)
halves = line.split(f=0.75, saa=False, throw=True)
print(halves[0].length, halves[1].length)
"""
if type(f) in dth.dt.NUMBERS:
point = (self.x0+f*self.dx, self.y0+f*self.dy, self.z0+f*self.dz)
if saa:
if retain == 0:
self.x1, self.y1, self.z1 = point
elif retain == 1:
self.x0, self.y0, self.z0 = point
else:
raise ValueError('Invalid retain spec for True saa.')
else:
line0 = Sline3d.by_coord(self.coord_list[0], point)
line1 = Sline3d.by_coord(point, self.coord_list[1])
return [line0, line1]
if type(f) in dth.dt.ITERABLES:
# TODO
pass