import uuid
from .color import Color
from .xform import Xform
from .point import Point
from .vector import Vector
[docs]
class Line:
"""A 3D line segment with visual properties.
Parameters
----------
x0 : float, optional
X coordinate of start point. Defaults to 0.0.
y0 : float, optional
Y coordinate of start point. Defaults to 0.0.
z0 : float, optional
Z coordinate of start point. Defaults to 0.0.
x1 : float, optional
X coordinate of end point. Defaults to 0.0.
y1 : float, optional
Y coordinate of end point. Defaults to 0.0.
z1 : float, optional
Z coordinate of end point. Defaults to 1.0.
Attributes
----------
guid : str
Unique identifier of the line.
name : str
Name of the line.
linecolor : Color
Color of the line.
width : float
Width of the line for display.
"""
[docs]
def __init__(self, x0=0.0, y0=0.0, z0=0.0, x1=0.0, y1=0.0, z1=1.0):
self.guid = str(uuid.uuid4())
self.name = "my_line"
self._x0 = x0
self._y0 = y0
self._z0 = z0
self._x1 = x1
self._y1 = y1
self._z1 = z1
self.width = 1.0
self.linecolor = Color.white()
self.xform = Xform.identity()
@property
def x0(self):
"""Get the X coordinate of start point."""
return self._x0
@x0.setter
def x0(self, value):
"""Set the X coordinate of start point."""
self._x0 = value
@property
def y0(self):
"""Get the Y coordinate of start point."""
return self._y0
@y0.setter
def y0(self, value):
"""Set the Y coordinate of start point."""
self._y0 = value
@property
def z0(self):
"""Get the Z coordinate of start point."""
return self._z0
@z0.setter
def z0(self, value):
"""Set the Z coordinate of start point."""
self._z0 = value
@property
def x1(self):
"""Get the X coordinate of end point."""
return self._x1
@x1.setter
def x1(self, value):
"""Set the X coordinate of end point."""
self._x1 = value
@property
def y1(self):
"""Get the Y coordinate of end point."""
return self._y1
@y1.setter
def y1(self, value):
"""Set the Y coordinate of end point."""
self._y1 = value
@property
def z1(self):
"""Get the Z coordinate of end point."""
return self._z1
@z1.setter
def z1(self, value):
"""Set the Z coordinate of end point."""
self._z1 = value
[docs]
@classmethod
def from_points(cls, p1, p2):
"""Create a line from two points.
Parameters
----------
p1 : Point
Start point.
p2 : Point
End point.
Returns
-------
Line
New line from p1 to p2.
"""
return cls(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z)
[docs]
@classmethod
def with_name(cls, name, x0, y0, z0, x1, y1, z1):
"""Create a line with a specific name.
Parameters
----------
name : str
Name for the line.
x0, y0, z0 : float
Start point coordinates.
x1, y1, z1 : float
End point coordinates.
Returns
-------
Line
New named line.
"""
line = cls(x0, y0, z0, x1, y1, z1)
line.name = name
return line
[docs]
def length(self):
"""Calculate the length of the line.
Returns
-------
float
Length of the line.
"""
dx = self._x1 - self._x0
dy = self._y1 - self._y0
dz = self._z1 - self._z0
return (dx * dx + dy * dy + dz * dz) ** 0.5
[docs]
def squared_length(self):
"""Calculate the squared length of the line.
Returns
-------
float
Squared length of the line.
"""
dx = self._x1 - self._x0
dy = self._y1 - self._y0
dz = self._z1 - self._z0
return dx * dx + dy * dy + dz * dz
[docs]
def to_vector(self):
"""Convert line to vector from start to end.
Returns
-------
Vector
Direction vector of the line.
"""
return Vector(self._x1 - self._x0, self._y1 - self._y0, self._z1 - self._z0)
[docs]
def point_at(self, t):
"""Get point at parameter t along the line.
Parameters
----------
t : float
Parameter value (0.0 = start, 1.0 = end).
Returns
-------
Point
Point at parameter t.
"""
s = 1.0 - t
return Point(
s * self._x0 + t * self._x1,
s * self._y0 + t * self._y1,
s * self._z0 + t * self._z1,
)
[docs]
def start(self):
"""Get start point.
Returns
-------
Point
Start point of the line.
"""
return Point(self._x0, self._y0, self._z0)
[docs]
def end(self):
"""Get end point.
Returns
-------
Point
End point of the line.
"""
return Point(self._x1, self._y1, self._z1)
def __getitem__(self, index):
"""Get coordinate by index (0-5)."""
coords = [self._x0, self._y0, self._z0, self._x1, self._y1, self._z1]
return coords[index]
def __setitem__(self, index, value):
"""Set coordinate by index (0-5)."""
if index == 0:
self._x0 = value
elif index == 1:
self._y0 = value
elif index == 2:
self._z0 = value
elif index == 3:
self._x1 = value
elif index == 4:
self._y1 = value
elif index == 5:
self._z1 = value
else:
raise IndexError("Index out of bounds")
def __iadd__(self, other):
"""Add vector to line in place."""
if isinstance(other, Vector):
self._x0 += other.x
self._y0 += other.y
self._z0 += other.z
self._x1 += other.x
self._y1 += other.y
self._z1 += other.z
return self
def __isub__(self, other):
"""Subtract vector from line in place."""
if isinstance(other, Vector):
self._x0 -= other.x
self._y0 -= other.y
self._z0 -= other.z
self._x1 -= other.x
self._y1 -= other.y
self._z1 -= other.z
return self
def __imul__(self, factor):
"""Multiply line coordinates by scalar in place."""
self._x0 *= factor
self._y0 *= factor
self._z0 *= factor
self._x1 *= factor
self._y1 *= factor
self._z1 *= factor
return self
def __itruediv__(self, factor):
"""Divide line coordinates by scalar in place."""
self._x0 /= factor
self._y0 /= factor
self._z0 /= factor
self._x1 /= factor
self._y1 /= factor
self._z1 /= factor
return self
def __add__(self, other):
"""Add vector to line."""
if isinstance(other, Vector):
return Line(
self._x0 + other.x,
self._y0 + other.y,
self._z0 + other.z,
self._x1 + other.x,
self._y1 + other.y,
self._z1 + other.z,
)
return NotImplemented
def __sub__(self, other):
"""Subtract vector from line."""
if isinstance(other, Vector):
return Line(
self._x0 - other.x,
self._y0 - other.y,
self._z0 - other.z,
self._x1 - other.x,
self._y1 - other.y,
self._z1 - other.z,
)
return NotImplemented
def __mul__(self, factor):
"""Multiply line by scalar."""
return Line(
self._x0 * factor,
self._y0 * factor,
self._z0 * factor,
self._x1 * factor,
self._y1 * factor,
self._z1 * factor,
)
def __truediv__(self, factor):
"""Divide line by scalar."""
return Line(
self._x0 / factor,
self._y0 / factor,
self._z0 / factor,
self._x1 / factor,
self._y1 / factor,
self._z1 / factor,
)
def __str__(self):
"""String representation."""
return f"Line({self._x0}, {self._y0}, {self._z0}, {self._x1}, {self._y1}, {self._z1})"
def __repr__(self):
"""Detailed representation."""
return self.__str__()
###########################################################################################
# Polymorphic JSON Serialization
###########################################################################################
def __jsondump__(self):
"""Serialize to polymorphic JSON format with type field.
Returns
-------
dict
Dictionary with 'type', 'guid', 'name', and object fields.
"""
return {
"type": f"{self.__class__.__name__}",
"guid": self.guid,
"name": self.name,
"x0": self._x0,
"y0": self._y0,
"z0": self._z0,
"x1": self._x1,
"y1": self._y1,
"z1": self._z1,
"width": self.width,
"linecolor": self.linecolor.__jsondump__(),
}
@classmethod
def __jsonload__(cls, data, guid=None, name=None):
"""Deserialize from polymorphic JSON format.
Parameters
----------
data : dict
Dictionary containing line data.
guid : str, optional
GUID for the line.
name : str, optional
Name for the line.
Returns
-------
:class:`Line`
Reconstructed line instance.
"""
from .encoders import decode_node
line = cls(
data["x0"], data["y0"], data["z0"],
data["x1"], data["y1"], data["z1"]
)
line.guid = guid if guid is not None else data.get("guid", line.guid)
line.name = name if name is not None else data.get("name", line.name)
if "width" in data:
line.width = data["width"]
if "linecolor" in data:
line.linecolor = decode_node(data["linecolor"])
if "xform" in data:
obj.xform = decode_node(data["xform"])
return line