upxo.geoEntities.mulsline3d module

Multi-straight-line 3D geometric entity module for UPXO.

Provides MSline3d, a collection of ordered Sline3d segments representing an open or closed 3-D polyline. Supports construction from line lists, node/length queries, spatial distance calculations, subdivision, and node removal operations.

Applications

  • Non-conformal to conformal geometry conversion.

  • Hierarchical grain structure feature generation.

  • General 3-D polyline geometry operations.

Classes

MSline3d

Ordered collection of Sline3d segments forming a 3-D polyline.

Coordinate system

                 Y+
                 |           Z-
                 |         /
                 |       /
                 |     /
                 |   /
X-               | /               X+
-----------------O------------------
                /|
              /  |
            /    |
          /      |
        /        |
      /          |
    Z+           Y-

Usage

from upxo.geoEntities.mulsline3d import MSline3d as msl3d
from upxo.geoEntities.sline3d import Sline3d as sl3d

@author: Dr. Sunil Anandatheertha

class upxo.geoEntities.mulsline3d.MSline3d(llist)[source]

Bases: object

Ordered collection of Sline3d segments forming a 3-D polyline.

Wraps a list of connected Sline3d objects and provides aggregate geometric properties (total length, mean length, node positions) and editing operations (subdivision, node removal).

lines

Ordered sequence of 3-D line segments.

Type:

list of Sline3d

Usage
-----
::

from upxo.geoEntities.mulsline3d import MSline3d as msl3d

Examples

from upxo.geoEntities.mulsline3d import MSline3d as msl3d
from upxo.geoEntities.sline3d import Sline3d as sl3d
e0 = sl3d(0.0, 0.0, 0.0, 1.0, 1.0, 1.0)
e1 = sl3d(1.0, 1.0, 1.0, 1.5, 1.5, 0.0)
e2 = sl3d(1.5, 1.5, 0.0, 2.5, 2.5, 3.0)
e3 = sl3d(2.5, 2.5, 3.0, 4.0, 4.0, 3.5)
e4 = sl3d(4.0, 4.0, 3.5, 4.0, 6.0, 3.5)
me = msl3d([e0, e1, e2, e3, e4])
print(me.n, me.length)
lines
classmethod from_lines(llist, close=True)[source]

Construct a MSline3d from a list of Sline3d objects.

Parameters:
  • llist (list of Sline3d) – Ordered sequence of 3-D line segments.

  • close (bool, optional) – If True, append a closing segment from the last endpoint back to the first. Default is True.

Returns:

New multi-line with optional closing segment appended.

Return type:

MSline3d

Examples

from upxo.geoEntities.mulsline3d import MSline3d as msl3d
from upxo.geoEntities.sline3d import Sline3d as sl3d
lines = [sl3d(0, 0, 0, 1, 1, 1), sl3d(1, 1, 1, 2, 2, 2)]
me = msl3d.from_lines(lines, close=True)
print(me.n)
classmethod by_walk(var_l='constant', var_ang='constant', specs={'max_total_length': 10, 'mean_length': 1, 'min_total_length': 8, 'n': 5})[source]

Construct a multi-line by random walk with length/angle variation. Not yet implemented.

property n

Number of line segments in the collection.

property lengths

Lengths of individual segments in order.

Returns:

Length of each Sline3d in self.lines.

Return type:

list of float

Examples

me.lengths
property length

Total length of all segments combined.

Returns:

Sum of all individual segment lengths.

Return type:

float

Examples

me.length
property length_mean

Mean segment length.

Returns:

total_length / n.

Return type:

float

Examples

me.length_mean
property gradients

Gradient [dx/dz, dy/dz] of every segment.

Returns:

Per-segment gradient pairs.

Return type:

list of list of float

property nodes

Unique list of nodes (start and end points) across all segments.

Returns:

Unique [x, y, z] node coordinates.

Return type:

numpy.ndarray, shape (M, 3)

property mid_nodes

Midpoint coordinates of all segments.

Returns:

Midpoint (xmid, ymid, zmid) for each segment in order.

Return type:

list of tuple

Examples

from upxo.geoEntities.mulsline3d import MSline3d as msl3d
from upxo.geoEntities.sline3d import Sline3d as sl3d
lines = [sl3d(0.0, 0.0, 0.0, 1.0, 1.0, 1.0),
         sl3d(1.0, 1.0, 1.0, 1.5, 1.5, 0.0),
         sl3d(1.5, 1.5, 0.0, 2.5, 2.5, 3.0),
         sl3d(2.5, 2.5, 3.0, 4.0, 4.0, 3.5),
         sl3d(4.0, 4.0, 3.5, 4.0, 6.0, 3.5)]
MULLINE = msl3d.from_lines(lines, close=True)
print(MULLINE.mid_nodes)
property line_ids

Memory id of each segment.

Returns:

id(line) for each segment in self.lines.

Return type:

list of int

unclose()[source]

Remove the closing segment (last element of self.lines) in place.

distances_nodes(points)[source]

Compute distances from every node to each of the given points.

Parameters:

points (numpy.ndarray, shape (M, 3)) – Query points to measure distances from.

Returns:

Distance from each node (row) to each query point (column).

Return type:

numpy.ndarray, shape (N_nodes, M)

Notes

Uses vectorised broadcasting: points[:, np.newaxis] shapes to (M, 1, 3) so that subtraction with nodes (shape (N, 3)) yields (M, N, 3) differences, squared-summed over axis 2, then transposed to (N_nodes, M).

Examples

from upxo.geoEntities.mulsline3d import MSline3d as msl3d
from upxo.geoEntities.sline3d import Sline3d as sl3d
import numpy as np
lines = [sl3d(0.0, 0.0, 0.0, 1.0, 1.0, 1.0),
         sl3d(1.0, 1.0, 1.0, 1.5, 1.5, 0.0),
         sl3d(1.5, 1.5, 0.0, 2.5, 2.5, 3.0),
         sl3d(2.5, 2.5, 3.0, 4.0, 4.0, 3.5),
         sl3d(4.0, 4.0, 3.5, 4.0, 6.0, 3.5)]
MULLINE = msl3d.from_lines(lines, close=True)
points = np.random.random((2, 3))
MULLINE.distances_nodes(points)
find_closest_nodes(point)[source]

Find the index (or indices) of the node(s) closest to point.

Parameters:

point (array-like, shape (3,)) – Query coordinate [x, y, z].

Returns:

Index (or indices) into self.nodes of the nearest node(s).

Return type:

int or list of int

Examples

Example 1 — random query point:

from upxo.geoEntities.mulsline3d import MSline3d as msl3d
from upxo.geoEntities.sline3d import Sline3d as sl3d
import numpy as np
lines = [sl3d(0.0, 0.0, 0.0, 1.0, 1.0, 1.0),
         sl3d(1.0, 1.0, 1.0, 1.5, 1.5, 0.0),
         sl3d(1.5, 1.5, 0.0, 2.5, 2.5, 3.0)]
MULLINE = msl3d.from_lines(lines, close=True)
point = np.random.random(3) * np.random.randint(10)
MULLINE.find_closest_nodes(point)

Example 2 — midpoint of the first segment:

from upxo.geoEntities.mulsline3d import MSline3d as msl3d
from upxo.geoEntities.sline3d import Sline3d as sl3d
lines = [sl3d(0.0, 0.0, 0.0, 1.0, 1.0, 1.0),
         sl3d(1.0, 1.0, 1.0, 1.5, 1.5, 0.0),
         sl3d(1.5, 1.5, 0.0, 2.5, 2.5, 3.0)]
MULLINE = msl3d.from_lines(lines, close=True)
point = lines[0].mid
MULLINE.find_closest_nodes(point)
sub_divide(line_number=0, f=0.5)[source]

Sub-divide a single segment at a fractional position.

Replaces the segment at line_number with two shorter segments split at the fractional position f.

Parameters:
  • line_number (int, optional) – Zero-based index of the segment to split. Default is 0.

  • f (float, optional) – Fractional split position in (0, 1). Default is 0.5.

Returns:

Modifies self.lines in place.

Return type:

None

Raises:
  • TypeError – If line_number is not an integer.

  • ValueError – If line_number exceeds the number of segments.

Examples

from upxo.geoEntities.mulsline3d import MSline3d as msl3d
from upxo.geoEntities.sline3d import Sline3d as sl3d
e0 = sl3d(0.0, 0.0, 0.0, 1.0, 1.0, 1.0)
e1 = sl3d(1.0, 1.0, 1.0, 1.5, 1.5, 0.0)
e2 = sl3d(1.5, 1.5, 0.0, 2.5, 2.5, 3.0)
e3 = sl3d(2.5, 2.5, 3.0, 4.0, 4.0, 3.5)
e4 = sl3d(4.0, 4.0, 3.5, 4.0, 6.0, 3.5)
me = msl3d.from_lines([e0, e1, e2, e3, e4], close=False)
me.sub_divide(line_number=0, f=0.25)
me.sub_divide(line_number=len(me.lines), f=0.25)
me.sub_divide(line_number=3, f=0.50)
remove_point_by_index(index=2, remove='previous_line')[source]

Remove a node by index, merging the adjacent segments.

Parameters:
  • index (int, optional) – Zero-based index of the node (shared endpoint) to remove. Default is 2.

  • remove ({'previous_line', 'next_line', 'both'}, optional) –

    How to handle the adjacent segments:

    • 'previous_line' — extend the next segment back to the previous segment’s start point and delete the previous segment.

    • 'next_line' — extend the previous segment forward to the next segment’s end point and delete the next segment.

    • 'both' — replace both adjacent segments with a single new segment connecting the previous segment’s start to the next segment’s end.

    Default is 'previous_line'.

Returns:

Modifies self.lines in place.

Return type:

None

Examples

Example 1 — remove previous segment:

from upxo.geoEntities.mulsline3d import MSline3d as msl3d
from upxo.geoEntities.sline3d import Sline3d as sl3d
lines = [sl3d(0.0, 0.0, 0.0, 1.0, 1.0, 1.0),
         sl3d(1.0, 1.0, 1.0, 1.5, 1.5, 0.0),
         sl3d(1.5, 1.5, 0.0, 2.5, 2.5, 3.0),
         sl3d(2.5, 2.5, 3.0, 4.0, 4.0, 3.5),
         sl3d(4.0, 4.0, 3.5, 4.0, 6.0, 3.5)]
MULLINE = msl3d.from_lines(lines, close=True)
MULLINE.remove_point_by_index(index=2, remove='previous_line')
print(MULLINE.n)

Example 2 — remove next segment:

from upxo.geoEntities.mulsline3d import MSline3d as msl3d
from upxo.geoEntities.sline3d import Sline3d as sl3d
lines = [sl3d(0.0, 0.0, 0.0, 1.0, 1.0, 1.0),
         sl3d(1.0, 1.0, 1.0, 1.5, 1.5, 0.0),
         sl3d(1.5, 1.5, 0.0, 2.5, 2.5, 3.0),
         sl3d(2.5, 2.5, 3.0, 4.0, 4.0, 3.5),
         sl3d(4.0, 4.0, 3.5, 4.0, 6.0, 3.5)]
MULLINE = msl3d.from_lines(lines, close=True)
MULLINE.remove_point_by_index(index=2, remove='next_line')

Example 3 — replace both adjacent segments with a single new segment:

from upxo.geoEntities.mulsline3d import MSline3d as msl3d
from upxo.geoEntities.sline3d import Sline3d as sl3d
lines = [sl3d(0.0, 0.0, 0.0, 1.0, 1.0, 1.0),
         sl3d(1.0, 1.0, 1.0, 1.5, 1.5, 0.0),
         sl3d(1.5, 1.5, 0.0, 2.5, 2.5, 3.0),
         sl3d(2.5, 2.5, 3.0, 4.0, 4.0, 3.5),
         sl3d(4.0, 4.0, 3.5, 4.0, 6.0, 3.5)]
MULLINE = msl3d.from_lines(lines, close=True)
MULLINE.remove_point_by_index(index=2, remove='both')
remove_point_by_location(location=(None, None, None), remove='previous_line')[source]

Remove a node nearest to location, merging the adjacent segments.

Finds the closest node to location using find_closest_nodes() and delegates to remove_point_by_index().

Parameters:
  • location (array-like, shape (3,), optional) – Target [x, y, z] coordinate. The nearest node is found and removed. Default is (None, None, None).

  • remove ({'previous_line', 'next_line', 'both'}, optional) – See remove_point_by_index(). Default is 'previous_line'.

Returns:

Modifies self.lines in place.

Return type:

None

Notes

When only 2 segments remain after removal and the second is a redundant closing segment (same endpoints as the first but reversed), the closing segment is automatically deleted.

Examples

Example 1 — remove by a known endpoint:

from upxo.geoEntities.mulsline3d import MSline3d as msl3d
from upxo.geoEntities.sline3d import Sline3d as sl3d
lines = [sl3d(0.0, 0.0, 0.0, 1.0, 1.0, 1.0),
         sl3d(1.0, 1.0, 1.0, 1.5, 1.5, 0.0),
         sl3d(1.5, 1.5, 0.0, 2.5, 2.5, 3.0)]
MULLINE = msl3d.from_lines(lines, close=True)
MULLINE.remove_point_by_location(location=lines[0].coord_i,
                                 remove='previous_line')

Example 2 — remove by a segment midpoint:

from upxo.geoEntities.mulsline3d import MSline3d as msl3d
from upxo.geoEntities.sline3d import Sline3d as sl3d
lines = [sl3d(0.0, 0.0, 0.0, 1.0, 1.0, 1.0),
         sl3d(1.0, 1.0, 1.0, 1.5, 1.5, 0.0),
         sl3d(1.5, 1.5, 0.0, 2.5, 2.5, 3.0)]
MULLINE = msl3d.from_lines(lines, close=True)
MULLINE.remove_point_by_location(location=lines[0].mid,
                                 remove='previous_line')

Example 3 — remove by a random location:

from upxo.geoEntities.mulsline3d import MSline3d as msl3d
from upxo.geoEntities.sline3d import Sline3d as sl3d
import numpy as np
lines = [sl3d(0.0, 0.0, 0.0, 1.0, 1.0, 1.0),
         sl3d(1.0, 1.0, 1.0, 1.5, 1.5, 0.0),
         sl3d(1.5, 1.5, 0.0, 2.5, 2.5, 3.0)]
MULLINE = msl3d.from_lines(lines, close=True)
location = np.random.random(3) * np.random.randint(10)
MULLINE.remove_point_by_location(location=location,
                                 remove='previous_line')
x0
y0
z0
x1
y1
z1
f
closed