Coverage for pyTooling/Cartesian3D/__init__.py: 94%

231 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-31 22:23 +0000

1# ==================================================================================================================== # 

2# _____ _ _ ____ _ _ _____ ____ # 

3# _ __ _ |_ _|__ ___ | (_)_ __ __ _ / ___|__ _ _ __| |_ ___ ___(_) __ _ _ __ |___ /| _ \ # 

4# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` || | / _` | '__| __/ _ \/ __| |/ _` | '_ \ |_ \| | | | # 

5# | |_) | |_| || | (_) | (_) | | | | | | (_| || |__| (_| | | | || __/\__ \ | (_| | | | |___) | |_| | # 

6# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)____\__,_|_| \__\___||___/_|\__,_|_| |_|____/|____/ # 

7# |_| |___/ |___/ # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2025-2025 Patrick Lehmann - Bötzingen, Germany # 

15# # 

16# Licensed under the Apache License, Version 2.0 (the "License"); # 

17# you may not use this file except in compliance with the License. # 

18# You may obtain a copy of the License at # 

19# # 

20# http://www.apache.org/licenses/LICENSE-2.0 # 

21# # 

22# Unless required by applicable law or agreed to in writing, software # 

23# distributed under the License is distributed on an "AS IS" BASIS, # 

24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

25# See the License for the specific language governing permissions and # 

26# limitations under the License. # 

27# # 

28# SPDX-License-Identifier: Apache-2.0 # 

29# ==================================================================================================================== # 

30# 

31"""An implementation of 3D cartesian data structures for Python.""" 

32from sys import version_info 

33 

34from math import sqrt, acos 

35from typing import Union, Generic, Any, Tuple 

36 

37try: 

38 from pyTooling.Decorators import readonly, export 

39 from pyTooling.Exceptions import ToolingException 

40 from pyTooling.MetaClasses import ExtendedType 

41 from pyTooling.Common import getFullyQualifiedName 

42 from pyTooling.Cartesian2D import Coordinate 

43except (ImportError, ModuleNotFoundError): # pragma: no cover 

44 print("[pyTooling.Cartesian2D] Could not import from 'pyTooling.*'!") 

45 

46 try: 

47 from Decorators import readonly, export 

48 from Exceptions import ToolingException 

49 from MetaClasses import ExtendedType 

50 from Common import getFullyQualifiedName 

51 from Cartesian2D import Coordinate 

52 except (ImportError, ModuleNotFoundError) as ex: # pragma: no cover 

53 print("[pyTooling.Cartesian2D] Could not import directly!") 

54 raise ex 

55 

56 

57@export 

58class Point3D(Generic[Coordinate], metaclass=ExtendedType, slots=True): 

59 """An implementation of a 3D cartesian point.""" 

60 

61 x: Coordinate #: The x-direction coordinate. 

62 y: Coordinate #: The y-direction coordinate. 

63 z: Coordinate #: The z-direction coordinate. 

64 

65 def __init__(self, x: Coordinate, y: Coordinate, z: Coordinate) -> None: 

66 """ 

67 Initializes a 3-dimensional point. 

68 

69 :param x: X-coordinate. 

70 :param y: Y-coordinate. 

71 :param z: Z-coordinate. 

72 :raises TypeError: If x/y/z-coordinate is not of type integer or float. 

73 """ 

74 if not isinstance(x, (int, float)): 

75 ex = TypeError(f"Parameter 'x' is not of type integer or float.") 

76 ex.add_note(f"Got type '{getFullyQualifiedName(x)}'.") 

77 raise ex 

78 if not isinstance(y, (int, float)): 

79 ex = TypeError(f"Parameter 'y' is not of type integer or float.") 

80 ex.add_note(f"Got type '{getFullyQualifiedName(y)}'.") 

81 raise ex 

82 if not isinstance(z, (int, float)): 

83 ex = TypeError(f"Parameter 'z' is not of type integer or float.") 

84 ex.add_note(f"Got type '{getFullyQualifiedName(z)}'.") 

85 raise ex 

86 

87 self.x = x 

88 self.y = y 

89 self.z = z 

90 

91 def Copy(self) -> "Point3D[Coordinate]": # TODO: Python 3.11: -> Self: 

92 """ 

93 Create a new 3D-point as a copy of this 3D point. 

94 

95 :return: Copy of this 3D-point. 

96 

97 .. seealso:: 

98 

99 :meth:`+ operator <__add__>` 

100 Create a new 3D-point moved by a positive 3D-offset. 

101 :meth:`- operator <__sub__>` 

102 Create a new 3D-point moved by a negative 3D-offset. 

103 """ 

104 return self.__class__(self.x, self.y, self.z) 

105 

106 def ToTuple(self) -> Tuple[Coordinate, Coordinate, Coordinate]: 

107 """ 

108 Convert this 3D-Point to a simple 3-element tuple. 

109 

110 :return: ``(x, y, z)`` tuple. 

111 """ 

112 return self.x, self.y, self.z 

113 

114 def __add__(self, other: Any) -> "Point3D[Coordinate]": 

115 """ 

116 Adds a 3D-offset to this 3D-point and creates a new 3D-point. 

117 

118 :param other: A 3D-offset as :class:`Offset3D` or :class:`tuple`. 

119 :return: A new 3D-point shifted by the 3D-offset. 

120 :raises TypeError: If parameter 'other' is not a :class:`Offset3D` or :class:`tuple`. 

121 """ 

122 if isinstance(other, Offset3D): 

123 return self.__class__( 

124 self.x + other.xOffset, 

125 self.y + other.yOffset, 

126 self.z + other.zOffset 

127 ) 

128 elif isinstance(other, tuple): 

129 return self.__class__( 

130 self.x + other[0], 

131 self.y + other[1], 

132 self.z + other[2] 

133 ) 

134 else: 

135 ex = TypeError(f"Parameter 'other' is not of type Offset3D or tuple.") 

136 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

137 raise ex 

138 

139 def __iadd__(self, other: Any) -> "Point3D[Coordinate]": # TODO: Python 3.11: -> Self: 

140 """ 

141 Adds a 3D-offset to this 3D-point (inplace). 

142 

143 :param other: A 3D-offset as :class:`Offset3D` or :class:`tuple`. 

144 :return: This 3D-point. 

145 :raises TypeError: If parameter 'other' is not a :class:`Offset3D` or :class:`tuple`. 

146 """ 

147 if isinstance(other, Offset3D): 

148 self.x += other.xOffset 

149 self.y += other.yOffset 

150 self.z += other.zOffset 

151 elif isinstance(other, tuple): 

152 self.x += other[0] 

153 self.y += other[1] 

154 self.z += other[2] 

155 else: 

156 ex = TypeError(f"Parameter 'other' is not of type Offset3D or tuple.") 

157 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

158 raise ex 

159 

160 return self 

161 

162 def __sub__(self, other: Any) -> Union["Offset3D[Coordinate]", "Point3D[Coordinate]"]: 

163 """ 

164 Subtract two 3D-Points from each other and create a new 3D-offset. 

165 

166 :param other: A 3D-point as :class:`Point3D`. 

167 :return: A new 3D-offset representing the distance between these two points. 

168 :raises TypeError: If parameter 'other' is not a :class:`Point3D`. 

169 """ 

170 if isinstance(other, Point3D): 

171 return Offset3D( 

172 self.x - other.x, 

173 self.y - other.y, 

174 self.z - other.z 

175 ) 

176 else: 

177 ex = TypeError(f"Parameter 'other' is not of type Point3D.") 

178 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

179 raise ex 

180 

181 def __isub__(self, other: Any) -> "Point3D[Coordinate]": # TODO: Python 3.11: -> Self: 

182 """ 

183 Subtracts a 3D-offset to this 3D-point (inplace). 

184 

185 :param other: A 3D-offset as :class:`Offset3D` or :class:`tuple`. 

186 :return: This 3D-point. 

187 :raises TypeError: If parameter 'other' is not a :class:`Offset3D` or :class:`tuple`. 

188 """ 

189 if isinstance(other, Offset3D): 

190 self.x -= other.xOffset 

191 self.y -= other.yOffset 

192 self.z -= other.zOffset 

193 elif isinstance(other, tuple): 

194 self.x -= other[0] 

195 self.y -= other[1] 

196 self.z -= other[2] 

197 else: 

198 ex = TypeError(f"Parameter 'other' is not of type Offset3D or tuple.") 

199 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

200 raise ex 

201 

202 return self 

203 

204 def __repr__(self) -> str: 

205 """ 

206 Returns the 3D point's string representation. 

207 

208 :returns: The string representation of the 3D point. 

209 """ 

210 return f"Point3D({self.x}, {self.y}, {self.z})" 

211 

212 def __str__(self) -> str: 

213 """ 

214 Returns the 3D point's string equivalent. 

215 

216 :returns: The string equivalent of the 3D point. 

217 """ 

218 return f"({self.x}, {self.y}, {self.z})" 

219 

220 

221@export 

222class Origin3D(Point3D[Coordinate], Generic[Coordinate]): 

223 """An implementation of a 3D cartesian origin.""" 

224 

225 def __init__(self) -> None: 

226 """ 

227 Initializes a 3-dimensional origin. 

228 """ 

229 super().__init__(0, 0, 0) 

230 

231 def Copy(self) -> "Origin3D[Coordinate]": # TODO: Python 3.11: -> Self: 

232 """ 

233 :raises RuntimeError: Because an origin can't be copied. 

234 """ 

235 raise RuntimeError(f"An origin can't be copied.") 

236 

237 def __repr__(self) -> str: 

238 """ 

239 Returns the 3D origin's string representation. 

240 

241 :returns: The string representation of the 3D origin. 

242 """ 

243 return f"Origin3D({self.x}, {self.y}, {self.z})" 

244 

245 

246@export 

247class Offset3D(Generic[Coordinate], metaclass=ExtendedType, slots=True): 

248 """An implementation of a 3D cartesian offset.""" 

249 

250 xOffset: Coordinate #: The x-direction offset 

251 yOffset: Coordinate #: The y-direction offset 

252 zOffset: Coordinate #: The z-direction offset 

253 

254 def __init__(self, xOffset: Coordinate, yOffset: Coordinate, zOffset: Coordinate) -> None: 

255 """ 

256 Initializes a 3-dimensional offset. 

257 

258 :param xOffset: x-direction offset. 

259 :param yOffset: y-direction offset. 

260 :param zOffset: z-direction offset. 

261 :raises TypeError: If x/y/z-offset is not of type integer or float. 

262 """ 

263 if not isinstance(xOffset, (int, float)): 

264 ex = TypeError(f"Parameter 'xOffset' is not of type integer or float.") 

265 ex.add_note(f"Got type '{getFullyQualifiedName(xOffset)}'.") 

266 raise ex 

267 if not isinstance(yOffset, (int, float)): 

268 ex = TypeError(f"Parameter 'yOffset' is not of type integer or float.") 

269 ex.add_note(f"Got type '{getFullyQualifiedName(yOffset)}'.") 

270 raise ex 

271 if not isinstance(zOffset, (int, float)): 

272 ex = TypeError(f"Parameter 'zOffset' is not of type integer or float.") 

273 ex.add_note(f"Got type '{getFullyQualifiedName(zOffset)}'.") 

274 raise ex 

275 

276 self.xOffset = xOffset 

277 self.yOffset = yOffset 

278 self.zOffset = zOffset 

279 

280 def Copy(self) -> "Offset3D[Coordinate]": # TODO: Python 3.11: -> Self: 

281 """ 

282 Create a new 3D-offset as a copy of this 3D-offset. 

283 

284 :returns: Copy of this 3D-offset. 

285 

286 .. seealso:: 

287 

288 :meth:`+ operator <__add__>` 

289 Create a new 3D-offset moved by a positive 3D-offset. 

290 :meth:`- operator <__sub__>` 

291 Create a new 3D-offset moved by a negative 3D-offset. 

292 """ 

293 return self.__class__(self.xOffset, self.yOffset, self.zOffset) 

294 

295 def ToTuple(self) -> Tuple[Coordinate, Coordinate, Coordinate]: 

296 """ 

297 Convert this 3D-offset to a simple 3-element tuple. 

298 

299 :returns: ``(x, y, z)`` tuple. 

300 """ 

301 return self.xOffset, self.yOffset, self.zOffset 

302 

303 def __eq__(self, other) -> bool: 

304 """ 

305 Compare two 3D-offsets for equality. 

306 

307 :param other: Parameter to compare against. 

308 :returns: ``True``, if both 3D-offsets are equal. 

309 :raises TypeError: If parameter ``other`` is not of type :class:`Offset3D` or :class:`tuple`. 

310 """ 

311 if isinstance(other, Offset3D): 

312 return self.xOffset == other.xOffset and self.yOffset == other.yOffset and self.zOffset == other.zOffset 

313 elif isinstance(other, tuple): 

314 return self.xOffset == other[0] and self.yOffset == other[1] and self.zOffset == other[2] 

315 else: 

316 ex = TypeError(f"Parameter 'other' is not of type Offset3D or tuple.") 

317 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

318 raise ex 

319 

320 def __ne__(self, other) -> bool: 

321 """ 

322 Compare two 3D-offsets for inequality. 

323 

324 :param other: Parameter to compare against. 

325 :returns: ``True``, if both 3D-offsets are unequal. 

326 :raises TypeError: If parameter ``other`` is not of type :class:`Offset3D` or :class:`tuple`. 

327 """ 

328 return not self.__eq__(other) 

329 

330 def __neg__(self) -> "Offset3D[Coordinate]": 

331 """ 

332 Negate all components of this 3D-offset and create a new 3D-offset. 

333 

334 :returns: 3D-offset with negated offset components. 

335 """ 

336 return self.__class__( 

337 -self.xOffset, 

338 -self.yOffset, 

339 -self.zOffset 

340 ) 

341 

342 def __add__(self, other: Any) -> "Offset3D[Coordinate]": 

343 """ 

344 Adds a 3D-offset to this 3D-offset and creates a new 3D-offset. 

345 

346 :param other: A 3D-offset as :class:`Offset3D` or :class:`tuple`. 

347 :returns: A new 3D-offset extended by the 3D-offset. 

348 :raises TypeError: If parameter 'other' is not a :class:`Offset3D` or :class:`tuple`. 

349 """ 

350 if isinstance(other, Offset3D): 

351 return self.__class__( 

352 self.xOffset + other.xOffset, 

353 self.yOffset + other.yOffset, 

354 self.zOffset + other.zOffset 

355 ) 

356 elif isinstance(other, tuple): 

357 return self.__class__( 

358 self.xOffset + other[0], 

359 self.yOffset + other[1], 

360 self.zOffset + other[2] 

361 ) 

362 else: 

363 ex = TypeError(f"Parameter 'other' is not of type Offset3D or tuple.") 

364 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

365 raise ex 

366 

367 def __iadd__(self, other: Any) -> "Offset3D[Coordinate]": # TODO: Python 3.11: -> Self: 

368 """ 

369 Adds a 3D-offset to this 3D-offset (inplace). 

370 

371 :param other: A 3D-offset as :class:`Offset3D` or :class:`tuple`. 

372 :returns: This 3D-point. 

373 :raises TypeError: If parameter 'other' is not a :class:`Offset3D` or :class:`tuple`. 

374 """ 

375 if isinstance(other, Offset3D): 

376 self.xOffset += other.xOffset 

377 self.yOffset += other.yOffset 

378 self.zOffset += other.zOffset 

379 elif isinstance(other, tuple): 

380 self.xOffset += other[0] 

381 self.yOffset += other[1] 

382 self.zOffset += other[2] 

383 else: 

384 ex = TypeError(f"Parameter 'other' is not of type Offset3D or tuple.") 

385 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

386 raise ex 

387 

388 return self 

389 

390 def __sub__(self, other: Any) -> "Offset3D[Coordinate]": 

391 """ 

392 Subtracts a 3D-offset from this 3D-offset and creates a new 3D-offset. 

393 

394 :param other: A 3D-offset as :class:`Offset3D` or :class:`tuple`. 

395 :returns: A new 3D-offset reduced by the 3D-offset. 

396 :raises TypeError: If parameter 'other' is not a :class:`Offset3D` or :class:`tuple`. 

397 """ 

398 if isinstance(other, Offset3D): 

399 return self.__class__( 

400 self.xOffset - other.xOffset, 

401 self.yOffset - other.yOffset, 

402 self.zOffset - other.zOffset 

403 ) 

404 elif isinstance(other, tuple): 

405 return self.__class__( 

406 self.xOffset - other[0], 

407 self.yOffset - other[1], 

408 self.zOffset - other[2] 

409 ) 

410 else: 

411 ex = TypeError(f"Parameter 'other' is not of type Offset3D or tuple.") 

412 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

413 raise ex 

414 

415 def __isub__(self, other: Any) -> "Offset3D[Coordinate]": # TODO: Python 3.11: -> Self: 

416 """ 

417 Subtracts a 3D-offset from this 3D-offset (inplace). 

418 

419 :param other: A 3D-offset as :class:`Offset3D` or :class:`tuple`. 

420 :returns: This 3D-point. 

421 :raises TypeError: If parameter 'other' is not a :class:`Offset3D` or :class:`tuple`. 

422 """ 

423 if isinstance(other, Offset3D): 

424 self.xOffset -= other.xOffset 

425 self.yOffset -= other.yOffset 

426 self.zOffset -= other.zOffset 

427 elif isinstance(other, tuple): 

428 self.xOffset -= other[0] 

429 self.yOffset -= other[1] 

430 self.zOffset -= other[2] 

431 else: 

432 ex = TypeError(f"Parameter 'other' is not of type Offset3D or tuple.") 

433 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

434 raise ex 

435 

436 return self 

437 

438 def __repr__(self) -> str: 

439 """ 

440 Returns the 3D offset's string representation. 

441 

442 :returns: The string representation of the 3D offset. 

443 """ 

444 return f"Offset3D({self.xOffset}, {self.yOffset}, {self.zOffset})" 

445 

446 def __str__(self) -> str: 

447 """ 

448 Returns the 3D offset's string equivalent. 

449 

450 :returns: The string equivalent of the 3D offset. 

451 """ 

452 return f"({self.xOffset}, {self.yOffset}, {self.zOffset})" 

453 

454 

455@export 

456class Size3D(Generic[Coordinate], metaclass=ExtendedType, slots=True): 

457 """An implementation of a 3D cartesian size.""" 

458 

459 width: Coordinate #: width in x-direction. 

460 height: Coordinate #: height in y-direction. 

461 depth: Coordinate #: depth in z-direction. 

462 

463 def __init__(self, width: Coordinate, height: Coordinate, depth: Coordinate) -> None: 

464 """ 

465 Initializes a 2-dimensional size. 

466 

467 :param width: width in x-direction. 

468 :param height: height in y-direction. 

469 :param depth: depth in z-direction. 

470 :raises TypeError: If width/height/depth is not of type integer or float. 

471 """ 

472 if not isinstance(width, (int, float)): 

473 ex = TypeError(f"Parameter 'width' is not of type integer or float.") 

474 ex.add_note(f"Got type '{getFullyQualifiedName(width)}'.") 

475 raise ex 

476 if not isinstance(height, (int, float)): 

477 ex = TypeError(f"Parameter 'height' is not of type integer or float.") 

478 ex.add_note(f"Got type '{getFullyQualifiedName(height)}'.") 

479 raise ex 

480 if not isinstance(depth, (int, float)): 

481 ex = TypeError(f"Parameter 'depth' is not of type integer or float.") 

482 ex.add_note(f"Got type '{getFullyQualifiedName(depth)}'.") 

483 raise ex 

484 

485 self.width = width 

486 self.height = height 

487 self.depth = depth 

488 

489 def Copy(self) -> "Size3D[Coordinate]": # TODO: Python 3.11: -> Self: 

490 """ 

491 Create a new 3D-size as a copy of this 3D-size. 

492 

493 :returns: Copy of this 3D-size. 

494 """ 

495 return self.__class__(self.width, self.height, self.depth) 

496 

497 def ToTuple(self) -> Tuple[Coordinate, Coordinate, Coordinate]: 

498 """ 

499 Convert this 3D-size to a simple 3-element tuple. 

500 

501 :return: ``(width, height, depth)`` tuple. 

502 """ 

503 return self.width, self.height, self.depth 

504 

505 def __repr__(self) -> str: 

506 """ 

507 Returns the 3D size's string representation. 

508 

509 :returns: The string representation of the 3D size. 

510 """ 

511 return f"Size3D({self.width}, {self.height}, {self.depth})" 

512 

513 def __str__(self) -> str: 

514 """ 

515 Returns the 3D size's string equivalent. 

516 

517 :returns: The string equivalent of the 3D size. 

518 """ 

519 return f"({self.width}, {self.height}, {self.depth})" 

520 

521 

522@export 

523class Segment3D(Generic[Coordinate], metaclass=ExtendedType, slots=True): 

524 """An implementation of a 3D cartesian segment.""" 

525 

526 start: Point3D[Coordinate] #: Start point of a segment. 

527 end: Point3D[Coordinate] #: End point of a segment. 

528 

529 def __init__(self, start: Point3D[Coordinate], end: Point3D[Coordinate], copyPoints: bool = True) -> None: 

530 """ 

531 Initializes a 3-dimensional segment. 

532 

533 :param start: Start point of the segment. 

534 :param end: End point of the segment. 

535 :raises TypeError: If start/end is not of type Point3D. 

536 """ 

537 if not isinstance(start, Point3D): 537 ↛ 538line 537 didn't jump to line 538 because the condition on line 537 was never true

538 ex = TypeError(f"Parameter 'start' is not of type Point3D.") 

539 ex.add_note(f"Got type '{getFullyQualifiedName(start)}'.") 

540 raise ex 

541 if not isinstance(end, Point3D): 541 ↛ 542line 541 didn't jump to line 542 because the condition on line 541 was never true

542 ex = TypeError(f"Parameter 'end' is not of type Point3D.") 

543 ex.add_note(f"Got type '{getFullyQualifiedName(end)}'.") 

544 raise ex 

545 

546 self.start = start.Copy() if copyPoints else start 

547 self.end = end.Copy() if copyPoints else end 

548 

549 

550@export 

551class LineSegment3D(Segment3D[Coordinate], Generic[Coordinate]): 

552 """An implementation of a 3D cartesian line segment.""" 

553 

554 @readonly 

555 def Length(self) -> float: 

556 """ 

557 Read-only property to return the Euclidean distance between start and end point. 

558 

559 :return: Euclidean distance between start and end point 

560 """ 

561 return sqrt((self.end.x - self.start.x) ** 2 + (self.end.y - self.start.y) ** 2 + (self.end.z - self.start.z) ** 2) 

562 

563 def AngleTo(self, other: "LineSegment3D[Coordinate]") -> float: 

564 vectorA = self.ToOffset() 

565 vectorB = other.ToOffset() 

566 scalarProductAB = vectorA.xOffset * vectorB.xOffset + vectorA.yOffset * vectorB.yOffset + vectorA.zOffset * vectorB.zOffset 

567 

568 return acos(scalarProductAB / (abs(self.Length) * abs(other.Length))) 

569 

570 def ToOffset(self) -> Offset3D[Coordinate]: 

571 """ 

572 Convert this 3D line segment to a 3D-offset. 

573 

574 :return: 3D-offset as :class:`Offset3D` 

575 """ 

576 return self.end - self.start 

577 

578 def ToTuple(self) -> Tuple[Tuple[Coordinate, Coordinate, Coordinate], Tuple[Coordinate, Coordinate, Coordinate]]: 

579 """ 

580 Convert this 3D line segment to a simple 2-element tuple of 3D-point tuples. 

581 

582 :return: ``((x1, y1, z1), (x2, y2, z2))`` tuple. 

583 """ 

584 return self.start.ToTuple(), self.end.ToTuple() 

585 

586 def __repr__(self) -> str: 

587 """ 

588 Returns the 3D line segment's string representation. 

589 

590 :returns: The string representation of the 3D line segment. 

591 """ 

592 return f"LineSegment3D({self.start}, {self.end})" 

593 

594 def __str__(self) -> str: 

595 """ 

596 Returns the 3D line segment's string equivalent. 

597 

598 :returns: The string equivalent of the 3D line segment. 

599 """ 

600 return f"({self.start}{self.end})"