Skip to content

Commit 19eea27

Browse files
committed
improvement and flaked . Now work from this point. modified exportcpacs ( rember to check how VSP2CPACS is working with these modifications)
1 parent 5465b7d commit 19eea27

6 files changed

Lines changed: 251 additions & 304 deletions

File tree

src/app/01_✈️_Geometry.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
render_openvsp_panel,
4242
convert_vsp3_to_cpacs,
4343
)
44-
from ceasiompy.stl2cpacs.func.split_stl_into_components import split_main, split_stl_by_symmetry_plane
44+
from ceasiompy.stl2cpacs.func.split_stl_into_components import split_main
4545
from ceasiompy.stl2cpacs.stl2cpacs import main as stl2cpacs_main
4646
from ceasiompy.stl2cpacs.stl2cpacs import cpacs_component_detection
4747

@@ -73,9 +73,11 @@
7373

7474

7575
# Methods
76-
def _show_stl_mesh(stl_path: str, key: str,show_wireframe: bool | None = None) -> None:
76+
def _show_stl_mesh(stl_path: str,
77+
key: str,
78+
show_wireframe: bool | None = None
79+
) -> None:
7780
"""Interactive STL viewer with proper wireframe toggle refresh."""
78-
7981

8082
# --- Cache STL loading only ---
8183
@st.cache_data(show_spinner=False)
@@ -84,7 +86,6 @@ def load_stl(path):
8486

8587
stl_mesh = load_stl(stl_path)
8688

87-
8889
# Build unique vertices + face indices
8990
vertices = stl_mesh.vectors.reshape(-1, 3)
9091
verts_unique, index = np.unique(vertices, axis=0, return_inverse=True)
@@ -142,10 +143,12 @@ def load_stl(path):
142143
dynamic_key = f"{key}_{show_wireframe}"
143144

144145
st.plotly_chart(fig, use_container_width=True, key=dynamic_key)
145-
146+
146147

147148
def _show_split_components_mesh(
148-
split_dir: str | Path, key: str, show_wireframe: bool | None = None
149+
split_dir: str | Path,
150+
key: str,
151+
show_wireframe: bool | None = None
149152
) -> None:
150153
"""Display split STL components with one color per component."""
151154

@@ -227,8 +230,8 @@ def generate_distinct_colors(n: int) -> list[str]:
227230
)
228231
dynamic_key = f"{key}_{show_wireframe}_{len(component_files)}"
229232
st.plotly_chart(fig, use_container_width=True, key=dynamic_key)
230-
231-
233+
234+
232235
def _clean_toolspecific(cpacs: CPACS) -> CPACS:
233236
air_name = cpacs.ac_name
234237

@@ -566,6 +569,7 @@ def _section_load_cpacs() -> CPACS | None:
566569

567570

568571
def _section_stl_to_cpacs():
572+
569573
# 1. Load the stl
570574
st.markdown("#### Load an STL file or multiple file")
571575
st.markdown("You can upload a 3D STL model (binary or ASCII) to convert it to CPACS later.")
@@ -631,10 +635,10 @@ def _section_stl_to_cpacs():
631635
st.session_state.pop("last_split_components_dir", None)
632636
st.session_state["show_split_tools"] = False
633637
st.session_state["show_cpacs_conversion_tools"] = False
634-
638+
635639
# 2. visualization
636640
wireframe_view = st.toggle("Wireframe view", value=False, key="stl_wireframe_view")
637-
641+
638642
# 3. split tools (hidden by default)
639643
if "show_split_tools" not in st.session_state:
640644
st.session_state["show_split_tools"] = False
@@ -671,7 +675,7 @@ def _section_stl_to_cpacs():
671675
except Exception as exc:
672676
st.error(f"STL split failed: {exc}")
673677

674-
678+
675679
# 4. Convert to CPACS using automatic component detection
676680
st.markdown("#### Convert to CPACS")
677681

@@ -685,7 +689,6 @@ def _section_stl_to_cpacs():
685689
st.warning("No STL sources found in the working directory.")
686690
return None
687691

688-
689692
current_candidates = [str(path) for path in candidate_paths]
690693
selected_stl_files = current_candidates
691694
try:
@@ -769,7 +772,8 @@ def _section_stl_to_cpacs():
769772
min_value=0.0,
770773
max_value=0.5,
771774
format="%.4f",
772-
help="Trailing-edge trim ratio applied on each extracted airfoil. Increase if there are oscillations on the CPACS in the TE region.",
775+
help=("Trailing-edge trim ratio applied on each extracted airfoil.",
776+
" Increase if there are oscillations on the CPACS in the TE region."),
773777
key=f"stl_component_adv_te_cut_{idx}",
774778
)
775779
)
@@ -779,7 +783,9 @@ def _section_stl_to_cpacs():
779783
value=10,
780784
min_value=3,
781785
step=1,
782-
help="Bins used to split the section cloud into upper/lower surfaces. Increase if you have a very refined profile with sharp features, or if you have oscillations on the CPACS airfoil.",
786+
help=("Bins used to split the section cloud into upper/lower surfaces.",
787+
" Increase if you have a very refined profile with sharp features,",
788+
" or if you have oscillations on the CPACS airfoil."),
783789
key=f"stl_component_adv_n_bin_{idx}",
784790
)
785791
)
@@ -860,7 +866,6 @@ def _section_stl_to_cpacs():
860866

861867
return st.session_state.get("cpacs")
862868

863-
864869
# Functions
865870

866871
def section_select_cpacs() -> None:

src/ceasiompy/stl2cpacs/func/split_stl_into_components.py

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,23 @@
77
"""
88

99
from __future__ import annotations
10-
1110
from dataclasses import dataclass
1211
from pathlib import Path
1312
import struct
1413
from typing import Dict, List, Tuple
15-
1614
import numpy as np
1715

18-
1916
# Quantization tolerance used to merge numerically close vertices.
2017
VERTEX_MERGE_TOL = 1e-6
2118

22-
# ======================================================================================
23-
# VS CODE QUICK-RUN SETTINGS
24-
# ======================================================================================
25-
# Set your STL path here, then run this file directly in VS Code.
26-
INPUT_STL_PATH = "src/ceasiompy/STL2CPACS/SOARminSting-Shell.stl"
27-
28-
# Set output directory for split parts. One STL per detected component.
29-
OUTPUT_SPLIT_DIR = "src/ceasiompy/STL2CPACS/split_output"
30-
3119
# Connectivity tolerance used while grouping triangles into disconnected parts.
3220
DEFAULT_VERTEX_TOL = VERTEX_MERGE_TOL
3321
DEFAULT_FEATURE_ANGLE_DEG = 55.0
3422
SIGNIFICANT_COMPONENT_MIN_TRIS = 100
3523

3624
# ======================================================================================
37-
# HOW THE SPLIT WORKS (high-level)
25+
# HOW THE SPLIT WORKS
3826
# ======================================================================================
39-
# The splitter does NOT try to understand aircraft semantics (wing/fuselage/etc.).
40-
# It only uses mesh connectivity.
41-
#
4227
# 1) Read STL triangles as an array with shape (N, 3, 3)
4328
# N triangles, each triangle has 3 vertices, each vertex has (x, y, z).
4429
#
@@ -56,7 +41,6 @@
5641
# - A fully connected STL => one component file.
5742
# - A multi-part STL => one file per disconnected part.
5843

59-
6044
@dataclass
6145
class ComponentInfo:
6246
"""Container for one split component."""
@@ -79,8 +63,6 @@ def read_ascii_stl(path: str | Path) -> np.ndarray:
7963
_, x, y, z = line.split()[:4]
8064
tri.append([float(x), float(y), float(z)])
8165

82-
if len(tri) % 3 != 0:
83-
raise ValueError(f"Malformed ASCII STL: {path}")
8466

8567
return np.asarray(tri, dtype=float).reshape(-1, 3, 3)
8668

@@ -96,14 +78,14 @@ def read_binary_stl(path: str | Path) -> np.ndarray:
9678
tri = []
9779
offset = 0
9880
for _ in range(ntri):
99-
offset += 12 # normal
81+
offset += 12
10082
v1 = struct.unpack_from("<fff", data, offset)
10183
offset += 12
10284
v2 = struct.unpack_from("<fff", data, offset)
10385
offset += 12
10486
v3 = struct.unpack_from("<fff", data, offset)
10587
offset += 12
106-
offset += 2 # attribute byte count
88+
offset += 2
10789
tri.append([v1, v2, v3])
10890

10991
return np.asarray(tri, dtype=float)
@@ -125,7 +107,10 @@ def load_stl_auto(path: str | Path) -> np.ndarray:
125107
return read_binary_stl(path)
126108

127109

128-
def write_binary_stl(path: str | Path, triangles: np.ndarray, solid_name: str = "component") -> None:
110+
def write_binary_stl(path: str | Path,
111+
triangles: np.ndarray,
112+
solid_name: str = "component"
113+
) -> None:
129114
"""Write triangles to a binary STL file."""
130115

131116
tris = np.asarray(triangles, dtype=np.float32).reshape(-1, 3, 3)
@@ -171,7 +156,9 @@ def _triangle_adjacency_from_shared_vertices(triangles: np.ndarray, tol: float =
171156
return [list(nei) for nei in adjacency]
172157

173158

174-
def _triangle_adjacency_from_shared_edges(triangles: np.ndarray, tol: float = VERTEX_MERGE_TOL) -> List[List[int]]:
159+
def _triangle_adjacency_from_shared_edges(triangles: np.ndarray,
160+
tol: float = VERTEX_MERGE_TOL
161+
) -> List[List[int]]:
175162
"""Build triangle adjacency from shared *manifold* edges.
176163
177164
Used as a fallback when vertex-connectivity yields a single component.
@@ -252,7 +239,9 @@ def _triangle_normals(triangles: np.ndarray) -> np.ndarray:
252239

253240

254241
def _triangle_adjacency_from_smooth_shared_edges(
255-
triangles: np.ndarray, tol: float = VERTEX_MERGE_TOL, max_dihedral_deg: float = DEFAULT_FEATURE_ANGLE_DEG
242+
triangles: np.ndarray,
243+
tol: float = VERTEX_MERGE_TOL,
244+
max_dihedral_deg: float = DEFAULT_FEATURE_ANGLE_DEG
256245
) -> List[List[int]]:
257246
"""Build adjacency using only manifold edges with smooth dihedral angle."""
258247

@@ -336,7 +325,9 @@ def _extract_components_from_adjacency(adjacency: List[List[int]]) -> List[np.nd
336325
return components
337326

338327

339-
def _connected_triangle_components(triangles: np.ndarray, tol: float = VERTEX_MERGE_TOL) -> List[np.ndarray]:
328+
def _connected_triangle_components(triangles: np.ndarray,
329+
tol: float = VERTEX_MERGE_TOL
330+
) -> List[np.ndarray]:
340331
"""Split triangles into components with robust auto-connectivity.
341332
342333
We treat triangles as nodes of a graph:
@@ -359,7 +350,9 @@ def _connected_triangle_components(triangles: np.ndarray, tol: float = VERTEX_ME
359350
return _extract_components_from_adjacency(edge_adj)
360351

361352

362-
def _count_significant_components(components: List[np.ndarray], min_triangles: int = SIGNIFICANT_COMPONENT_MIN_TRIS) -> int:
353+
def _count_significant_components(components: List[np.ndarray],
354+
min_triangles: int = SIGNIFICANT_COMPONENT_MIN_TRIS
355+
) -> int:
363356
"""Count components with enough triangles to be meaningful geometric parts."""
364357

365358
return int(sum(1 for c in components if len(c) >= min_triangles))
@@ -434,7 +427,9 @@ def _histogram_valley_threshold(values: np.ndarray, n_bins: int = 80) -> float |
434427
return float(0.5 * (edges[best_i] + edges[best_i + 1]))
435428

436429

437-
def _induced_components_from_mask(adjacency: List[List[int]], mask: np.ndarray) -> List[np.ndarray]:
430+
def _induced_components_from_mask(adjacency: List[List[int]],
431+
mask: np.ndarray
432+
) -> List[np.ndarray]:
438433
"""Connected components of the subgraph induced by `mask`."""
439434

440435
mask = np.asarray(mask, dtype=bool)
@@ -458,7 +453,9 @@ def _induced_components_from_mask(adjacency: List[List[int]], mask: np.ndarray)
458453

459454

460455
def _span_split_largest_component(
461-
triangles: np.ndarray, comp_indices: List[np.ndarray], tol: float = VERTEX_MERGE_TOL
456+
triangles: np.ndarray,
457+
comp_indices: List[np.ndarray],
458+
tol: float = VERTEX_MERGE_TOL
462459
) -> List[np.ndarray]:
463460
"""Split one dominant shell into inboard/outboard parts using |y| valley."""
464461

@@ -498,7 +495,9 @@ def _span_split_largest_component(
498495
return remapped
499496

500497

501-
def _build_generic_components(disconnected_components: List[np.ndarray], triangles: np.ndarray) -> List[ComponentInfo]:
498+
def _build_generic_components(disconnected_components: List[np.ndarray],
499+
triangles: np.ndarray
500+
) -> List[ComponentInfo]:
502501
"""Build generic component objects from connected triangle groups.
503502
504503
This function only packages metadata and names:
@@ -667,7 +666,6 @@ def split_aircraft_stl(
667666
return components
668667

669668

670-
671669
def split_main(stl_path: str | Path, namefile: str, out_dir: str | Path) -> Path:
672670

673671
vertex_tol = DEFAULT_VERTEX_TOL
@@ -683,11 +681,10 @@ def split_main(stl_path: str | Path, namefile: str, out_dir: str | Path) -> Path
683681
split_dir = Path(out_dir) / "STL2CPACS"
684682
print(f"Output dir: {split_dir}")
685683

686-
comps = split_aircraft_stl(stl_path, output_dir=out_dir, vertex_tol=vertex_tol,name=namefile)
684+
comps = split_aircraft_stl(stl_path, output_dir=out_dir, vertex_tol=vertex_tol, name=namefile)
687685
if not comps:
688686
print("No triangles found.")
689687
else:
690688
print(f"\nWrote {len(comps)} split STL file(s) into: {split_dir}")
691689

692690
return split_dir
693-

0 commit comments

Comments
 (0)