navaltoolbox 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/PKG-INFO +1 -1
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/navaltoolbox/__init__.py +6 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/navaltoolbox/navaltoolbox.pyi +239 -1
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/navaltoolbox/visualization.py +226 -18
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/Cargo.lock +1 -1
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/Cargo.toml +1 -3
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/README.md +1 -1
- navaltoolbox-0.4.0/rust/src/appendage.rs +449 -0
- navaltoolbox-0.4.0/rust/src/deckedge.rs +350 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/downflooding/mod.rs +5 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/hydrostatics/calculator.rs +77 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/hydrostatics/dataclasses.rs +8 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/lib.rs +4 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/python.rs +384 -3
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/scripting/engine.rs +110 -35
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/silhouette/core.rs +5 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/tanks/tank.rs +10 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/vessel.rs +159 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/tests/integration_tests.rs +2 -2
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/README.md +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/navaltoolbox/plotting.py +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/navaltoolbox/py.typed +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/pyproject.toml +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/downflooding/loader.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/hull.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/hydrostatics/mod.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/hydrostatics/waterplane.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/mesh/clipper.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/mesh/loader.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/mesh/mod.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/mesh/transform.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/scripting/context.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/scripting/mod.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/scripting/result.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/silhouette/loader.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/silhouette/mod.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/stability/calculator.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/stability/complete.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/stability/dataclasses.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/stability/mod.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/tanks/dataclasses.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/src/tanks/mod.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/tests/data/box_10x10.stl +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/tests/data/dtmb5415.stl +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/tests/dtmb_validation.rs +0 -0
- {navaltoolbox-0.3.0 → navaltoolbox-0.4.0}/rust/tests/extended_hydrostatics.rs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: navaltoolbox
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Intended Audience :: Science/Research
|
|
6
6
|
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
@@ -58,6 +58,9 @@ from .navaltoolbox import (
|
|
|
58
58
|
CriteriaResult,
|
|
59
59
|
CriteriaContext,
|
|
60
60
|
ScriptEngine,
|
|
61
|
+
Appendage,
|
|
62
|
+
DeckEdge,
|
|
63
|
+
DeckEdgeSide,
|
|
61
64
|
)
|
|
62
65
|
|
|
63
66
|
__all__ = [
|
|
@@ -77,4 +80,7 @@ __all__ = [
|
|
|
77
80
|
"CriteriaResult",
|
|
78
81
|
"CriteriaContext",
|
|
79
82
|
"ScriptEngine",
|
|
83
|
+
"Appendage",
|
|
84
|
+
"DeckEdge",
|
|
85
|
+
"DeckEdgeSide",
|
|
80
86
|
]
|
|
@@ -27,6 +27,9 @@ __all__ = [
|
|
|
27
27
|
"Hull",
|
|
28
28
|
"Vessel",
|
|
29
29
|
"Silhouette",
|
|
30
|
+
"Appendage",
|
|
31
|
+
"DeckEdge",
|
|
32
|
+
"DeckEdgeSide",
|
|
30
33
|
"OpeningType",
|
|
31
34
|
"DownfloodingOpening",
|
|
32
35
|
"HydrostaticState",
|
|
@@ -342,6 +345,58 @@ class Vessel:
|
|
|
342
345
|
List of DownfloodingOpening objects.
|
|
343
346
|
"""
|
|
344
347
|
...
|
|
348
|
+
|
|
349
|
+
# Appendage methods
|
|
350
|
+
|
|
351
|
+
def add_appendage(self, appendage: "Appendage") -> None:
|
|
352
|
+
"""Add an appendage to the vessel."""
|
|
353
|
+
...
|
|
354
|
+
|
|
355
|
+
def num_appendages(self) -> int:
|
|
356
|
+
"""Returns the number of appendages."""
|
|
357
|
+
...
|
|
358
|
+
|
|
359
|
+
def clear_appendages(self) -> None:
|
|
360
|
+
"""Removes all appendages."""
|
|
361
|
+
...
|
|
362
|
+
|
|
363
|
+
def get_appendages(self) -> List["Appendage"]:
|
|
364
|
+
"""Get all appendages."""
|
|
365
|
+
...
|
|
366
|
+
|
|
367
|
+
def get_total_appendage_volume(self) -> float:
|
|
368
|
+
"""Returns the total appendage volume in m³."""
|
|
369
|
+
...
|
|
370
|
+
|
|
371
|
+
def get_total_appendage_wetted_surface(self) -> float:
|
|
372
|
+
"""Returns the total appendage wetted surface in m²."""
|
|
373
|
+
...
|
|
374
|
+
|
|
375
|
+
# Deck Edge methods
|
|
376
|
+
|
|
377
|
+
def add_deck_edge(self, deck_edge: "DeckEdge") -> None:
|
|
378
|
+
"""Add a deck edge to the vessel."""
|
|
379
|
+
...
|
|
380
|
+
|
|
381
|
+
def num_deck_edges(self) -> int:
|
|
382
|
+
"""Returns the number of deck edges."""
|
|
383
|
+
...
|
|
384
|
+
|
|
385
|
+
def has_deck_edges(self) -> bool:
|
|
386
|
+
"""Returns true if any deck edges are defined."""
|
|
387
|
+
...
|
|
388
|
+
|
|
389
|
+
def clear_deck_edges(self) -> None:
|
|
390
|
+
"""Removes all deck edges."""
|
|
391
|
+
...
|
|
392
|
+
|
|
393
|
+
def get_deck_edges(self) -> List["DeckEdge"]:
|
|
394
|
+
"""Get all deck edges."""
|
|
395
|
+
...
|
|
396
|
+
|
|
397
|
+
def get_min_freeboard(self, heel: float, trim: float, waterline_z: float) -> float | None:
|
|
398
|
+
"""Calculate minimum freeboard across all deck edges."""
|
|
399
|
+
...
|
|
345
400
|
|
|
346
401
|
|
|
347
402
|
class Silhouette:
|
|
@@ -426,6 +481,137 @@ class Silhouette:
|
|
|
426
481
|
...
|
|
427
482
|
|
|
428
483
|
|
|
484
|
+
|
|
485
|
+
class Appendage:
|
|
486
|
+
"""An appendage (additional volume element) attached to the vessel.
|
|
487
|
+
|
|
488
|
+
Appendages represent volume contributions from items like keels, rudders,
|
|
489
|
+
bulbous bows, etc. that are not part of the main hull geometry.
|
|
490
|
+
"""
|
|
491
|
+
|
|
492
|
+
@staticmethod
|
|
493
|
+
def from_point(name: str, center: Tuple[float, float, float], volume: float) -> "Appendage":
|
|
494
|
+
"""Create an appendage from a point (fixed volume at position)."""
|
|
495
|
+
...
|
|
496
|
+
|
|
497
|
+
@staticmethod
|
|
498
|
+
def from_file(name: str, file_path: str) -> "Appendage":
|
|
499
|
+
"""Create an appendage from an STL or VTK file."""
|
|
500
|
+
...
|
|
501
|
+
|
|
502
|
+
@staticmethod
|
|
503
|
+
def from_box(name: str, xmin: float, xmax: float, ymin: float, ymax: float, zmin: float, zmax: float) -> "Appendage":
|
|
504
|
+
"""Create an appendage from a box (parallelepiped)."""
|
|
505
|
+
...
|
|
506
|
+
|
|
507
|
+
@staticmethod
|
|
508
|
+
def from_cube(name: str, center: Tuple[float, float, float], volume: float) -> "Appendage":
|
|
509
|
+
"""Create an appendage from a cube (center and volume)."""
|
|
510
|
+
...
|
|
511
|
+
|
|
512
|
+
@staticmethod
|
|
513
|
+
def from_sphere(name: str, center: Tuple[float, float, float], volume: float) -> "Appendage":
|
|
514
|
+
"""Create an appendage from a sphere (center and volume)."""
|
|
515
|
+
...
|
|
516
|
+
|
|
517
|
+
@property
|
|
518
|
+
def name(self) -> str:
|
|
519
|
+
"""The appendage name."""
|
|
520
|
+
...
|
|
521
|
+
|
|
522
|
+
@name.setter
|
|
523
|
+
def name(self, value: str) -> None:
|
|
524
|
+
...
|
|
525
|
+
|
|
526
|
+
@property
|
|
527
|
+
def volume(self) -> float:
|
|
528
|
+
"""Volume in m³."""
|
|
529
|
+
...
|
|
530
|
+
|
|
531
|
+
@property
|
|
532
|
+
def center(self) -> Tuple[float, float, float]:
|
|
533
|
+
"""Center of volume (x, y, z) in meters."""
|
|
534
|
+
...
|
|
535
|
+
|
|
536
|
+
@property
|
|
537
|
+
def wetted_surface(self) -> float | None:
|
|
538
|
+
"""Wetted surface area in m², or None if not set."""
|
|
539
|
+
...
|
|
540
|
+
|
|
541
|
+
@wetted_surface.setter
|
|
542
|
+
def wetted_surface(self, value: float | None) -> None:
|
|
543
|
+
...
|
|
544
|
+
|
|
545
|
+
def geometry_type(self) -> str:
|
|
546
|
+
"""Returns the geometry type (Point, Mesh, Box, etc.)."""
|
|
547
|
+
...
|
|
548
|
+
|
|
549
|
+
def get_mesh_data(self) -> Tuple[List[Tuple[float, float, float]], List[Tuple[int, int, int]]] | None:
|
|
550
|
+
"""Returns mesh data (vertices, faces) if geometry is a mesh.
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
Tuple of (vertices, faces), or None if not a mesh.
|
|
554
|
+
"""
|
|
555
|
+
...
|
|
556
|
+
|
|
557
|
+
@property
|
|
558
|
+
def bounds(self) -> Tuple[float, float, float, float, float, float] | None:
|
|
559
|
+
"""Returns bounds (xmin, xmax, ymin, ymax, zmin, zmax)."""
|
|
560
|
+
...
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
class DeckEdgeSide:
|
|
564
|
+
"""Side of the deck edge (Port, Starboard, or Both)."""
|
|
565
|
+
|
|
566
|
+
@staticmethod
|
|
567
|
+
def port() -> "DeckEdgeSide": ...
|
|
568
|
+
|
|
569
|
+
@staticmethod
|
|
570
|
+
def starboard() -> "DeckEdgeSide": ...
|
|
571
|
+
|
|
572
|
+
@staticmethod
|
|
573
|
+
def both() -> "DeckEdgeSide": ...
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
class DeckEdge:
|
|
577
|
+
"""A deck edge contour (livet) for freeboard calculation."""
|
|
578
|
+
|
|
579
|
+
@staticmethod
|
|
580
|
+
def from_points(name: str, points: List[Tuple[float, float, float]], side: DeckEdgeSide) -> "DeckEdge":
|
|
581
|
+
"""Create a deck edge from a list of 3D points."""
|
|
582
|
+
...
|
|
583
|
+
|
|
584
|
+
@staticmethod
|
|
585
|
+
def from_file(name: str, file_path: str) -> "DeckEdge":
|
|
586
|
+
"""Load a deck edge from a DXF or VTK file."""
|
|
587
|
+
...
|
|
588
|
+
|
|
589
|
+
@property
|
|
590
|
+
def name(self) -> str:
|
|
591
|
+
"""The deck edge name."""
|
|
592
|
+
...
|
|
593
|
+
|
|
594
|
+
@name.setter
|
|
595
|
+
def name(self, value: str) -> None:
|
|
596
|
+
...
|
|
597
|
+
|
|
598
|
+
def num_points(self) -> int:
|
|
599
|
+
"""Returns the number of points."""
|
|
600
|
+
...
|
|
601
|
+
|
|
602
|
+
def get_points(self) -> List[Tuple[float, float, float]]:
|
|
603
|
+
"""Returns points as list of (x, y, z) tuples."""
|
|
604
|
+
...
|
|
605
|
+
|
|
606
|
+
def get_side(self) -> str:
|
|
607
|
+
"""Returns the side as a string."""
|
|
608
|
+
...
|
|
609
|
+
|
|
610
|
+
def get_freeboard(self, heel: float, trim: float, pivot: Tuple[float, float, float], waterline_z: float) -> float:
|
|
611
|
+
"""Calculate freeboard at given conditions."""
|
|
612
|
+
...
|
|
613
|
+
|
|
614
|
+
|
|
429
615
|
class OpeningType:
|
|
430
616
|
"""Type of opening that can cause downflooding.
|
|
431
617
|
|
|
@@ -635,6 +821,8 @@ class HydrostaticState:
|
|
|
635
821
|
free_surface_correction_t: Transverse FSC in meters.
|
|
636
822
|
free_surface_correction_l: Longitudinal FSC in meters.
|
|
637
823
|
stiffness_matrix: 6x6 hydrostatic stiffness matrix (flattened).
|
|
824
|
+
sectional_areas: List of (x, area) pairs representing the sectional area curve.
|
|
825
|
+
freeboard: Minimum freeboard in meters, or None.
|
|
638
826
|
"""
|
|
639
827
|
|
|
640
828
|
draft: float
|
|
@@ -646,6 +834,9 @@ class HydrostaticState:
|
|
|
646
834
|
volume: float
|
|
647
835
|
displacement: float
|
|
648
836
|
|
|
837
|
+
sectional_areas: List[Tuple[float, float]]
|
|
838
|
+
freeboard: float | None
|
|
839
|
+
|
|
649
840
|
@property
|
|
650
841
|
def cob(self) -> Tuple[float, float, float]:
|
|
651
842
|
"""Center of buoyancy (lcb, tcb, vcb) in meters."""
|
|
@@ -753,6 +944,7 @@ class HydrostaticsCalculator:
|
|
|
753
944
|
trim: float = 0.0,
|
|
754
945
|
heel: float = 0.0,
|
|
755
946
|
vcg: float | None = None,
|
|
947
|
+
num_stations: int | None = None,
|
|
756
948
|
) -> HydrostaticState:
|
|
757
949
|
"""Calculate hydrostatics at a given draft, trim, and heel.
|
|
758
950
|
|
|
@@ -1274,7 +1466,53 @@ class CriteriaResult:
|
|
|
1274
1466
|
|
|
1275
1467
|
|
|
1276
1468
|
class CriteriaContext:
|
|
1277
|
-
"""Context for Rhai scripts, wrapping stability results.
|
|
1469
|
+
"""Context for Rhai scripts, wrapping stability results.
|
|
1470
|
+
|
|
1471
|
+
This context provides access to stability data within Rhai scripts.
|
|
1472
|
+
The following methods are available when writing Rhai scripts:
|
|
1473
|
+
|
|
1474
|
+
**GZ Curve Methods:**
|
|
1475
|
+
- `get_heels()` -> list of heel angles
|
|
1476
|
+
- `get_gz_values()` -> list of GZ values
|
|
1477
|
+
- `area_under_curve(from, to)` -> area in m·rad
|
|
1478
|
+
- `gz_at_angle(angle)` -> GZ value at angle
|
|
1479
|
+
- `find_max_gz()` -> map with `angle` and `value`
|
|
1480
|
+
- `find_angle_of_vanishing_stability()` -> angle or ()
|
|
1481
|
+
- `get_first_flooding_angle()` -> angle or ()
|
|
1482
|
+
- `find_equilibrium_angle(heeling_arm)` -> angle or ()
|
|
1483
|
+
- `find_second_intercept(heeling_arm)` -> angle or ()
|
|
1484
|
+
- `get_limiting_angle(default)` -> limiting angle
|
|
1485
|
+
|
|
1486
|
+
**Hydrostatic Properties:**
|
|
1487
|
+
- `get_gm0()` -> initial GM in meters
|
|
1488
|
+
- `get_gm0_dry()` -> GM without FSC
|
|
1489
|
+
- `get_draft()` -> draft in meters
|
|
1490
|
+
- `get_trim()` -> trim angle in degrees
|
|
1491
|
+
- `get_displacement()` -> displacement in kg
|
|
1492
|
+
- `get_cog()` -> [x, y, z] center of gravity
|
|
1493
|
+
|
|
1494
|
+
**Form Coefficients:**
|
|
1495
|
+
- `get_cb()` -> block coefficient
|
|
1496
|
+
- `get_cm()` -> midship coefficient
|
|
1497
|
+
- `get_cp()` -> prismatic coefficient
|
|
1498
|
+
- `get_lwl()` -> waterline length
|
|
1499
|
+
- `get_bwl()` -> waterline breadth
|
|
1500
|
+
- `get_vcb()` -> vertical center of buoyancy
|
|
1501
|
+
|
|
1502
|
+
**Wind Data:**
|
|
1503
|
+
- `has_wind_data()` -> bool
|
|
1504
|
+
- `get_emerged_area()` -> area in m²
|
|
1505
|
+
- `get_wind_lever_arm()` -> arm in meters
|
|
1506
|
+
- `calculate_wind_heeling_lever(pressure)` -> lever at given pressure
|
|
1507
|
+
|
|
1508
|
+
**Parameters:**
|
|
1509
|
+
- `get_param(key)` -> value or ()
|
|
1510
|
+
- `has_param(key)` -> bool
|
|
1511
|
+
|
|
1512
|
+
**Metadata:**
|
|
1513
|
+
- `get_vessel_name()` -> string
|
|
1514
|
+
- `get_loading_condition()` -> string
|
|
1515
|
+
"""
|
|
1278
1516
|
|
|
1279
1517
|
@staticmethod
|
|
1280
1518
|
def from_result(
|
|
@@ -24,8 +24,11 @@ def plot_vessel_3d(
|
|
|
24
24
|
show_tanks: bool = True,
|
|
25
25
|
show_silhouettes: bool = True,
|
|
26
26
|
show_openings: bool = True,
|
|
27
|
+
show_appendages: bool = True,
|
|
28
|
+
show_deck_edges: bool = True,
|
|
27
29
|
opacity_hull: float = 0.5,
|
|
28
30
|
opacity_tank: float = 0.3,
|
|
31
|
+
opacity_appendage: float = 0.5,
|
|
29
32
|
title: str = "Vessel Visualization",
|
|
30
33
|
enable_opacity_slider: bool = True,
|
|
31
34
|
show_axes: bool = True,
|
|
@@ -39,8 +42,11 @@ def plot_vessel_3d(
|
|
|
39
42
|
show_tanks: Whether to show tank meshes.
|
|
40
43
|
show_silhouettes: Whether to show silhouette profiles.
|
|
41
44
|
show_openings: Whether to show downflooding openings.
|
|
45
|
+
show_appendages: Whether to show appendages.
|
|
46
|
+
show_deck_edges: Whether to show deck edges.
|
|
42
47
|
opacity_hull: Opacity for hull meshes (0.0 to 1.0).
|
|
43
48
|
opacity_tank: Opacity for tank meshes (0.0 to 1.0).
|
|
49
|
+
opacity_appendage: Opacity for appendage meshes (0.0 to 1.0).
|
|
44
50
|
title: Title of the plot.
|
|
45
51
|
enable_opacity_slider: Whether to add a slider to control hull opacity.
|
|
46
52
|
show_axes: Whether to show axes, grid, and background.
|
|
@@ -183,6 +189,40 @@ def plot_vessel_3d(
|
|
|
183
189
|
)
|
|
184
190
|
)
|
|
185
191
|
|
|
192
|
+
# 5. Appendages
|
|
193
|
+
if show_appendages:
|
|
194
|
+
for app in vessel.get_appendages():
|
|
195
|
+
_add_appendage_trace(fig, app, opacity_appendage)
|
|
196
|
+
|
|
197
|
+
# 6. Deck Edges
|
|
198
|
+
if show_deck_edges:
|
|
199
|
+
for edge in vessel.get_deck_edges():
|
|
200
|
+
points = edge.get_points()
|
|
201
|
+
if not points:
|
|
202
|
+
continue
|
|
203
|
+
|
|
204
|
+
x, y, z = zip(*points)
|
|
205
|
+
side = edge.get_side()
|
|
206
|
+
|
|
207
|
+
# Determine color based on side
|
|
208
|
+
color = "orange" # Default/Both
|
|
209
|
+
if "Port" in side:
|
|
210
|
+
color = "red"
|
|
211
|
+
elif "Starboard" in side:
|
|
212
|
+
color = "green"
|
|
213
|
+
|
|
214
|
+
fig.add_trace(
|
|
215
|
+
go.Scatter3d(
|
|
216
|
+
x=x,
|
|
217
|
+
y=y,
|
|
218
|
+
z=z,
|
|
219
|
+
mode="lines+markers",
|
|
220
|
+
line=dict(color=color, width=4),
|
|
221
|
+
marker=dict(size=3, color=color),
|
|
222
|
+
name=f"Deck Edge: {edge.name} ({side})",
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
|
|
186
226
|
# 5. Reference Points (AP/FP)
|
|
187
227
|
# Using Y=0, Z=0 for reference
|
|
188
228
|
ap_x = vessel.ap
|
|
@@ -326,8 +366,11 @@ def plot_hydrostatic_condition(
|
|
|
326
366
|
show_tanks: bool = True,
|
|
327
367
|
show_silhouettes: bool = True,
|
|
328
368
|
show_openings: bool = True,
|
|
369
|
+
show_appendages: bool = True,
|
|
370
|
+
show_deck_edges: bool = True,
|
|
329
371
|
opacity_hull: float = 0.5,
|
|
330
372
|
opacity_tank: float = 0.3,
|
|
373
|
+
opacity_appendage: float = 0.5,
|
|
331
374
|
title: str = "Hydrostatic Condition",
|
|
332
375
|
enable_opacity_slider: bool = True,
|
|
333
376
|
cog: Optional[Tuple[float, float, float]] = None,
|
|
@@ -348,8 +391,11 @@ def plot_hydrostatic_condition(
|
|
|
348
391
|
show_tanks: Whether to show tank meshes.
|
|
349
392
|
show_silhouettes: Whether to show silhouette profiles.
|
|
350
393
|
show_openings: Whether to show downflooding openings.
|
|
394
|
+
show_appendages: Whether to show appendages.
|
|
395
|
+
show_deck_edges: Whether to show deck edges.
|
|
351
396
|
opacity_hull: Opacity for hull meshes (0.0 to 1.0).
|
|
352
397
|
opacity_tank: Opacity for tank meshes (0.0 to 1.0).
|
|
398
|
+
opacity_appendage: Opacity for appendage meshes (0.0 to 1.0).
|
|
353
399
|
title: Title of the plot.
|
|
354
400
|
enable_opacity_slider: Whether to add a slider to control hull opacity.
|
|
355
401
|
cog: Optional Center of Gravity (x, y, z) to display.
|
|
@@ -599,7 +645,103 @@ def plot_hydrostatic_condition(
|
|
|
599
645
|
)
|
|
600
646
|
)
|
|
601
647
|
|
|
602
|
-
# 5.
|
|
648
|
+
# 5. Appendages
|
|
649
|
+
if show_appendages:
|
|
650
|
+
for app in vessel.get_appendages():
|
|
651
|
+
geo_type = app.geometry_type()
|
|
652
|
+
|
|
653
|
+
if geo_type == "Mesh":
|
|
654
|
+
mesh_data = app.get_mesh_data()
|
|
655
|
+
if mesh_data:
|
|
656
|
+
verts, faces = mesh_data
|
|
657
|
+
if verts and faces:
|
|
658
|
+
t_verts = transform_points(verts)
|
|
659
|
+
x, y, z = t_verts[:, 0], t_verts[:, 1], t_verts[:, 2]
|
|
660
|
+
i_idx, j_idx, k_idx = zip(*faces)
|
|
661
|
+
|
|
662
|
+
fig.add_trace(
|
|
663
|
+
go.Mesh3d(
|
|
664
|
+
x=x, y=y, z=z, i=i_idx, j=j_idx, k=k_idx,
|
|
665
|
+
color="darkkhaki",
|
|
666
|
+
opacity=opacity_appendage,
|
|
667
|
+
name=f"{app.name} (Appendage)",
|
|
668
|
+
showscale=False,
|
|
669
|
+
flatshading=True,
|
|
670
|
+
)
|
|
671
|
+
)
|
|
672
|
+
elif geo_type == "Box" or geo_type == "Cube":
|
|
673
|
+
bounds = app.bounds
|
|
674
|
+
if bounds:
|
|
675
|
+
xmin, xmax, ymin, ymax, zmin, zmax = bounds
|
|
676
|
+
# Create a box mesh
|
|
677
|
+
box_verts = [
|
|
678
|
+
(xmin, ymin, zmin), (xmax, ymin, zmin),
|
|
679
|
+
(xmax, ymax, zmin), (xmin, ymax, zmin),
|
|
680
|
+
(xmin, ymin, zmax), (xmax, ymin, zmax),
|
|
681
|
+
(xmax, ymax, zmax), (xmin, ymax, zmax)
|
|
682
|
+
]
|
|
683
|
+
t_verts = transform_points(box_verts)
|
|
684
|
+
x, y, z = t_verts[:, 0], t_verts[:, 1], t_verts[:, 2]
|
|
685
|
+
|
|
686
|
+
i = [7, 0, 0, 0, 4, 4, 6, 6, 4, 0, 3, 2]
|
|
687
|
+
j = [3, 4, 1, 2, 5, 6, 5, 2, 0, 1, 6, 3]
|
|
688
|
+
k = [0, 7, 2, 3, 6, 7, 1, 1, 5, 5, 7, 6]
|
|
689
|
+
|
|
690
|
+
fig.add_trace(
|
|
691
|
+
go.Mesh3d(
|
|
692
|
+
x=x, y=y, z=z, i=i, j=j, k=k,
|
|
693
|
+
color="goldenrod",
|
|
694
|
+
opacity=opacity_appendage,
|
|
695
|
+
name=f"{app.name} (Appendage)",
|
|
696
|
+
showscale=False,
|
|
697
|
+
)
|
|
698
|
+
)
|
|
699
|
+
elif geo_type == "Sphere" or geo_type == "Point":
|
|
700
|
+
center = app.center
|
|
701
|
+
volume = app.volume
|
|
702
|
+
t_center = transform_points([center])[0]
|
|
703
|
+
|
|
704
|
+
fig.add_trace(
|
|
705
|
+
go.Scatter3d(
|
|
706
|
+
x=[t_center[0]],
|
|
707
|
+
y=[t_center[1]],
|
|
708
|
+
z=[t_center[2]],
|
|
709
|
+
mode="markers",
|
|
710
|
+
marker=dict(
|
|
711
|
+
size=10, color="goldenrod", symbol="circle"),
|
|
712
|
+
name=f"{app.name} (Appendage)",
|
|
713
|
+
text=f"Vol: {volume:.3f}m³",
|
|
714
|
+
)
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
# 6. Deck Edges
|
|
718
|
+
if show_deck_edges:
|
|
719
|
+
for edge in vessel.get_deck_edges():
|
|
720
|
+
points = edge.get_points()
|
|
721
|
+
if not points:
|
|
722
|
+
continue
|
|
723
|
+
|
|
724
|
+
t_points = transform_points(points)
|
|
725
|
+
x, y, z = t_points[:, 0], t_points[:, 1], t_points[:, 2]
|
|
726
|
+
side = edge.get_side()
|
|
727
|
+
|
|
728
|
+
color = "orange"
|
|
729
|
+
if "Port" in side:
|
|
730
|
+
color = "red"
|
|
731
|
+
elif "Starboard" in side:
|
|
732
|
+
color = "green"
|
|
733
|
+
|
|
734
|
+
fig.add_trace(
|
|
735
|
+
go.Scatter3d(
|
|
736
|
+
x=x, y=y, z=z,
|
|
737
|
+
mode="lines+markers",
|
|
738
|
+
line=dict(color=color, width=4),
|
|
739
|
+
marker=dict(size=3, color=color),
|
|
740
|
+
name=f"Deck Edge: {edge.name} ({side})",
|
|
741
|
+
)
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
# 7. Reference Points
|
|
603
745
|
ap_pt = transform_points([(vessel.ap, 0, 0)])[0]
|
|
604
746
|
fp_pt = transform_points([(vessel.fp, 0, 0)])[0]
|
|
605
747
|
|
|
@@ -615,7 +757,7 @@ def plot_hydrostatic_condition(
|
|
|
615
757
|
)
|
|
616
758
|
)
|
|
617
759
|
|
|
618
|
-
#
|
|
760
|
+
# 8. Center of Gravity
|
|
619
761
|
if cog:
|
|
620
762
|
cog_pt = transform_points([cog])[0]
|
|
621
763
|
fig.add_trace(
|
|
@@ -801,25 +943,91 @@ def _add_hull_trace(fig: go.Figure, hull: Hull, name: str, opacity: float):
|
|
|
801
943
|
if hasattr(hull, "get_vertices") and hasattr(hull, "get_faces"):
|
|
802
944
|
verts = hull.get_vertices()
|
|
803
945
|
faces = hull.get_faces()
|
|
946
|
+
if verts and faces:
|
|
947
|
+
x, y, z = zip(*verts)
|
|
948
|
+
i_idx, j_idx, k_idx = zip(*faces)
|
|
804
949
|
|
|
805
|
-
|
|
806
|
-
|
|
950
|
+
fig.add_trace(
|
|
951
|
+
go.Mesh3d(
|
|
952
|
+
x=x,
|
|
953
|
+
y=y,
|
|
954
|
+
z=z,
|
|
955
|
+
i=i_idx,
|
|
956
|
+
j=j_idx,
|
|
957
|
+
k=k_idx,
|
|
958
|
+
color="gray",
|
|
959
|
+
opacity=opacity,
|
|
960
|
+
name=name,
|
|
961
|
+
showscale=False,
|
|
962
|
+
flatshading=True,
|
|
963
|
+
)
|
|
964
|
+
)
|
|
965
|
+
|
|
966
|
+
|
|
967
|
+
def _add_appendage_trace(fig: go.Figure, app: Any, opacity: float):
|
|
968
|
+
"""Helper to add appendage to figure."""
|
|
969
|
+
geo_type = app.geometry_type()
|
|
970
|
+
|
|
971
|
+
if geo_type == "Mesh":
|
|
972
|
+
mesh_data = app.get_mesh_data()
|
|
973
|
+
if mesh_data:
|
|
974
|
+
verts, faces = mesh_data
|
|
975
|
+
if verts and faces:
|
|
976
|
+
x, y, z = zip(*verts)
|
|
977
|
+
i_idx, j_idx, k_idx = zip(*faces)
|
|
978
|
+
|
|
979
|
+
fig.add_trace(
|
|
980
|
+
go.Mesh3d(
|
|
981
|
+
x=x,
|
|
982
|
+
y=y,
|
|
983
|
+
z=z,
|
|
984
|
+
i=i_idx,
|
|
985
|
+
j=j_idx,
|
|
986
|
+
k=k_idx,
|
|
987
|
+
color="darkkhaki",
|
|
988
|
+
opacity=opacity,
|
|
989
|
+
name=f"{app.name} (Appendage)",
|
|
990
|
+
showscale=False,
|
|
991
|
+
flatshading=True,
|
|
992
|
+
)
|
|
993
|
+
)
|
|
994
|
+
elif geo_type == "Box" or geo_type == "Cube":
|
|
995
|
+
bounds = app.bounds
|
|
996
|
+
if bounds:
|
|
997
|
+
xmin, xmax, ymin, ymax, zmin, zmax = bounds
|
|
807
998
|
|
|
808
|
-
|
|
809
|
-
|
|
999
|
+
# Create a box mesh
|
|
1000
|
+
x = [xmin, xmin, xmax, xmax, xmin, xmin, xmax, xmax]
|
|
1001
|
+
y = [ymin, ymax, ymax, ymin, ymin, ymax, ymax, ymin]
|
|
1002
|
+
z = [zmin, zmin, zmin, zmin, zmax, zmax, zmax, zmax]
|
|
810
1003
|
|
|
1004
|
+
i = [7, 0, 0, 0, 4, 4, 6, 6, 4, 0, 3, 2]
|
|
1005
|
+
j = [3, 4, 1, 2, 5, 6, 5, 2, 0, 1, 6, 3]
|
|
1006
|
+
k = [0, 7, 2, 3, 6, 7, 1, 1, 5, 5, 7, 6]
|
|
1007
|
+
|
|
1008
|
+
fig.add_trace(
|
|
1009
|
+
go.Mesh3d(
|
|
1010
|
+
x=x, y=y, z=z, i=i, j=j, k=k,
|
|
1011
|
+
color="goldenrod",
|
|
1012
|
+
opacity=opacity,
|
|
1013
|
+
name=f"{app.name} (Appendage)",
|
|
1014
|
+
showscale=False,
|
|
1015
|
+
)
|
|
1016
|
+
)
|
|
1017
|
+
elif geo_type == "Sphere" or geo_type == "Point":
|
|
1018
|
+
# Draw as a marker or simple sphere if we want
|
|
1019
|
+
center = app.center
|
|
1020
|
+
volume = app.volume
|
|
1021
|
+
|
|
1022
|
+
# Simple marker for now
|
|
811
1023
|
fig.add_trace(
|
|
812
|
-
go.
|
|
813
|
-
x=
|
|
814
|
-
y=
|
|
815
|
-
z=
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
opacity=opacity,
|
|
821
|
-
name=name,
|
|
822
|
-
showscale=False,
|
|
823
|
-
flatshading=True,
|
|
1024
|
+
go.Scatter3d(
|
|
1025
|
+
x=[center[0]],
|
|
1026
|
+
y=[center[1]],
|
|
1027
|
+
z=[center[2]],
|
|
1028
|
+
mode="markers",
|
|
1029
|
+
marker=dict(size=10, color="goldenrod", symbol="circle"),
|
|
1030
|
+
name=f"{app.name} (Appendage)",
|
|
1031
|
+
text=f"Vol: {volume:.3f}m³",
|
|
824
1032
|
)
|
|
825
1033
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "navaltoolbox"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
description = "High-performance naval architecture library for hydrostatics, stability, and tank calculations"
|
|
6
6
|
authors = ["Antoine ANCEAU"]
|
|
@@ -23,8 +23,6 @@ parry3d-f64 = "0.18"
|
|
|
23
23
|
nalgebra = "0.33"
|
|
24
24
|
# STL file loading
|
|
25
25
|
stl_io = "0.8"
|
|
26
|
-
# VTK file loading (optional, for later)
|
|
27
|
-
# vtkio = "0.6"
|
|
28
26
|
# Fast polygon triangulation for waterline caps
|
|
29
27
|
earcutr = "0.4"
|
|
30
28
|
# Ordered floats for HashMap keys
|