Skip to content

Commit 72a8d38

Browse files
committed
update ballooning reference length
1 parent afb1747 commit 72a8d38

File tree

11 files changed

+121
-68
lines changed

11 files changed

+121
-68
lines changed

openglider/glider/cell/basic_cell.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,16 @@ def point_basic_cell(self, y: int=0, ik: float=0) -> euklid.vector.Vector3D:
2121
return self.midrib(y).get(ik)
2222

2323
def midrib(self, y_value: float, ballooning: bool=True, arc_argument: bool=True, close_trailing_edge: bool=False) -> Profile3D:
24-
if y_value <= 0: # left side
24+
# return early for left and right side
25+
if y_value <= 0:
2526
return self.prof1
26-
elif y_value >= 1: # right side
27+
elif y_value >= 1:
2728
return self.prof2
28-
else: # somewhere else
29-
30-
# Ballooning is considered to be arcs, following 2 (two!) simple rules:
31-
# 1: x1 = x*d
32-
# 2: x2 = R*normvekt*(cos(phi2)-cos(phi)
33-
# 3: norm(d)/r*(1-x) = 2*sin(phi(2))
29+
else:
30+
# Ballooning is considered to be arcs, these simple rules:
31+
# 1: x = y_value * diff_vector
32+
# 2: y = ballooning_radius * normal * (cos(phi2)-cos(phi))
33+
# 3: 2 * sin(phi2) * ballooning_radius = norm(diff_vector)
3434

3535
x_values: list[float] = []
3636
distances = []
@@ -41,7 +41,6 @@ def midrib(self, y_value: float, ballooning: bool=True, arc_argument: bool=True,
4141

4242
if not ballooning:
4343
midrib = self.prof1.curve.add(diff * y_value)
44-
4544
else:
4645
for i in range(len(self.prof1.curve.nodes)): # Arc -> phi(bal) -> r # oder so...
4746
x_left = self.prof1.x_values[i]
@@ -53,7 +52,7 @@ def midrib(self, y_value: float, ballooning: bool=True, arc_argument: bool=True,
5352
d = y_value
5453
h = 0.
5554

56-
elif ballooning_radius > 1e-10:
55+
elif ballooning_radius is not None:
5756
phi = self.ballooning_phi[i] # phi is half only the half
5857

5958
if arc_argument:
@@ -92,15 +91,15 @@ def normvectors(self) -> euklid.vector.PolyLine3D:
9291
return euklid.vector.PolyLine3D(normals)
9392

9493
@cached_property('ballooning_phi', 'prof1', 'prof2')
95-
def ballooning_radius(self) -> list[float]:
94+
def ballooning_radius(self) -> list[float | None]:
9695
prof1 = self.prof1.curve.nodes
9796
prof2 = self.prof2.curve.nodes
9897

99-
radius: list[float] = []
98+
radius: list[float | None] = []
10099

101100
for p1, p2, phi in zip(prof1, prof2, self.ballooning_phi):
102101
if phi < 1e-10:
103-
radius.append(0.)
102+
radius.append(None)
104103
else:
105104
r = (p1-p2).length() / (2 * math.sin(phi) + (phi==0))
106105
radius.append(r)

openglider/glider/cell/cell.py

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import logging
44
import math
55
from collections.abc import Sequence
6-
from typing import ClassVar
6+
from typing import ClassVar, Literal
77

88
import euklid
99
import openglider.utils
@@ -43,23 +43,19 @@ def at_position(self, y: Percentage) -> euklid.vector.PolyLine2D:
4343
class Cell(BaseModel):
4444
rib1: Rib
4545
rib2: Rib
46+
4647
ballooning: BallooningBase
47-
attachment_points: list[CellAttachmentPoint] = Field(default_factory=list)
48-
miniribs: list[MiniRib] = Field(default_factory=list)
48+
ballooning_modifiers: list[BallooningModifier] = Field(default_factory=list)
49+
ballooning_reference: Literal["local", "cell"] = "local"
50+
4951
panels: list[Panel] = Field(default_factory=list)
5052
diagonals: list[DiagonalRib] = Field(default_factory=list)
5153
straps: list[TensionStrap] = Field(default_factory=list)
5254
rigidfoils: list[PanelRigidFoil] = Field(default_factory=list)
53-
name: str = "unnamed"
54-
55-
ballooning_modifiers: list[BallooningModifier] = Field(default_factory=list)
55+
attachment_points: list[CellAttachmentPoint] = Field(default_factory=list)
56+
miniribs: list[MiniRib] = Field(default_factory=list)
5657

57-
diagonal_naming_scheme: ClassVar[str] = "{cell.name}d{diagonal_no}"
58-
strap_naming_scheme: ClassVar[str] = "{cell.name}s{side}{diagonal_no}"
59-
panel_naming_scheme: ClassVar[str] = "{cell.name}p{panel_no}"
60-
panel_naming_scheme_upper: ClassVar[str] = "{cell.name}pu{panel_no}"
61-
panel_naming_scheme_lower: ClassVar[str] = "{cell.name}pl{panel_no}"
62-
minirib_naming_scheme: ClassVar[str] = "{cell.name}mr{minirib_no}"
58+
name: str = "unnamed"
6359

6460
def __hash__(self) -> int:
6561
return hash_list(self.rib1, self.rib2, *self.miniribs, *self.diagonals)
@@ -74,18 +70,18 @@ def sort_func(panel: Panel) -> float:
7470
lower.sort(key=sort_func)
7571

7672
for panel_no, panel in enumerate(upper):
77-
panel.name = self.panel_naming_scheme_upper.format(cell=self, panel_no=panel_no+1)
73+
panel.name = f"{cell_no}pu{panel_no+1}"
7874
for panel_no, panel in enumerate(lower):
79-
panel.name = self.panel_naming_scheme_lower.format(cell=self, panel_no=panel_no+1)
75+
panel.name = f"{cell_no}pl{panel_no+1}"
8076

8177
else:
8278
self.panels.sort(key=lambda panel: panel.mean_x())
8379
for panel_no, panel in enumerate(self.panels):
84-
panel.name = self.panel_naming_scheme.format(cell=self, panel=panel, panel_no=panel_no+1)
80+
panel.name = f"{cell_no}p{panel_no+1}"
8581

8682
def rename_diagonals(self, diagonals: Sequence[DiagonalRib | TensionStrap], cell_no: int, naming_scheme: str) -> None:
87-
upper = []
88-
lower = []
83+
upper: list[DiagonalRib | TensionStrap] = []
84+
lower: list[DiagonalRib | TensionStrap] = []
8985

9086
for diagonal in diagonals:
9187
if diagonal.get_average_x() > 0:
@@ -105,14 +101,24 @@ def rename_diagonals(self, diagonals: Sequence[DiagonalRib | TensionStrap], cell
105101

106102

107103
def rename_parts(self, cell_no: int, seperate_upper_lower: bool=False) -> None:
108-
self.rename_diagonals(self.diagonals, cell_no, self.diagonal_naming_scheme)
109-
self.rename_diagonals(self.straps, cell_no, self.strap_naming_scheme)
104+
self.rename_diagonals(self.diagonals, cell_no, "{cell.name}d{diagonal_no}")
105+
self.rename_diagonals(self.straps, cell_no, "{cell.name}s{side}{diagonal_no}")
110106

111107
for minirib_no, minirib in enumerate(self.miniribs):
112-
minirib.name = self.minirib_naming_scheme.format(cell=self, minirib=minirib, minirib_no=minirib_no+1)
108+
minirib.name = f"{self.name}mr{minirib_no+1}"
113109

114110
self.rename_panels(cell_no, seperate_upper_lower=seperate_upper_lower)
115111

112+
@cached_property('prof1', 'prof2')
113+
def width(self) -> float:
114+
# get the distance between the two profiles
115+
# project the base point of prof2 on the line of prof1
116+
117+
diff = self.rib2.pos - self.rib1.pos
118+
rib1_chord_line = self.rib1.rotation_matrix.apply(euklid.vector.Vector2D([1, 0]))
119+
120+
return diff.cross(rib1_chord_line).length()
121+
116122
@cached_property('rib1', 'rib2', 'ballooning_phi')
117123
def basic_cell(self) -> BasicCell:
118124
profile1 = self.rib1.profile_3d
@@ -300,10 +306,19 @@ def ballooning_modified(self) -> BallooningBase:
300306
return ballooning
301307

302308
@cached_property('ballooning_modified')
303-
def ballooning_phi(self) -> HashedList:
309+
def ballooning_phi(self) -> HashedList[float]:
310+
# get ballooning arc angles for each x value of the profiles
311+
304312
x_values = [max(-1, min(1, x)) for x in self.rib1.profile_2d.x_values]
305-
balloon = [self.ballooning_modified[i] for i in x_values]
306-
return HashedList([BallooningBase.arcsinc(1. / (1+bal)) if bal > 0 else 0 for bal in balloon])
313+
balloon = [max(0., self.ballooning_modified[i]) for i in x_values]
314+
315+
if self.ballooning_reference == "cell":
316+
lengths = [(p1 - p2).length() for p1, p2 in zip(self.rib1.profile_3d.curve.nodes, self.rib2.profile_3d.curve.nodes)]
317+
width = self.width
318+
sinc = [length / (length + width * bal) for length, bal in zip(lengths, balloon)]
319+
else:
320+
sinc = [1. / (1+bal) for bal in balloon]
321+
return HashedList([BallooningBase.arcsinc(x) if x < 1. else 0. for x in sinc])
307322

308323
@cached_property('ballooning', '_child_cells')
309324
def ballooning_tension_factors(self) -> list[float]:

openglider/glider/cell/diagonals.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,8 @@ def get_side_controlpoints(
168168
p2_1 = left_2d.nodes[-1]
169169
p2_2 = right_2d.get(ik2)
170170

171-
cp1 = p1_1 + (p1_2-p1_1) * self.curve_factor
172-
cp2 = p2_1 + (p2_2 - p2_1) * self.curve_factor
171+
cp1 = p1_1 + (p1_2-p1_1) * self.curve_factor.si
172+
cp2 = p2_1 + (p2_2 - p2_1) * self.curve_factor.si
173173

174174
return cp1, cp2
175175

@@ -370,8 +370,8 @@ def get_side_controlpoints(
370370
normal_2 = rotation.apply(right_2-left_2).normalized() * normal_size
371371

372372
return (
373-
left_1 + (right_1 - left_1) * 0.5 + normal_1 * self.curve_factor,
374-
left_2 + (right_2 - left_2) * 0.5 - normal_2 * self.curve_factor,
373+
left_1 + (right_1 - left_1) * 0.5 + normal_1 * self.curve_factor.si,
374+
left_2 + (right_2 - left_2) * 0.5 - normal_2 * self.curve_factor.si,
375375
)
376376

377377
return None

openglider/glider/cell/panel.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class PanelCut(BaseModel):
3434
x_right: Percentage
3535
cut_type: PANELCUT_TYPES
3636
seam_allowance: Length = Field(default_factory=lambda: Length(0))
37-
cut_3d_amount: list[float] = Field(default_factory=lambda: [0, 0])
37+
cut_3d_amount: list[float] = Field(default_factory=lambda: [0., 0.])
3838
cut_3d_sigma: float = 0.077
3939
x_center: Percentage | None = None
4040

openglider/glider/parametric/glider.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import euklid
1010
from openglider.glider.parametric.config import ParametricGliderConfig, SewingAllowanceConfig
1111
from openglider.glider.parametric.table.base.parser import Parser
12+
from openglider.glider.parametric.table.cell.ballooning import BallooningData
1213
import openglider.materials
1314
import pyfoil
1415
from openglider.glider.ballooning.base import BallooningBase
@@ -101,7 +102,7 @@ def get_arc_angles(self) -> list[float]:
101102
"""
102103
return self.arc.get_rib_angles(self.shape.rib_x_values)
103104

104-
def merge_ballooning(self, factor: float, multiplier: float) -> BallooningBase:
105+
def merge_ballooning(self, factor: float, multiplier: float | None) -> BallooningBase:
105106
factor = max(0, min(len(self.balloonings)-1, factor))
106107
k = factor % 1
107108
i = int(factor // 1)
@@ -112,7 +113,10 @@ def merge_ballooning(self, factor: float, multiplier: float) -> BallooningBase:
112113
else:
113114
result = first
114115

115-
return result * multiplier
116+
if multiplier is not None:
117+
result *= multiplier
118+
119+
return result
116120

117121
def get_merge_profile(self, factor: float) -> pyfoil.Airfoil:
118122
factor = max(0, min(len(self.profiles)-1, factor))
@@ -240,18 +244,19 @@ def get_profile_merge_values(self) -> list[float]:
240244
profile_merge_curve = euklid.vector.Interpolation(self.profile_merge_curve.get_sequence(self.num_interpolate).nodes)
241245
return [profile_merge_curve.get_value(abs(x)) for x in self.shape.rib_x_values]
242246

243-
def get_ballooning_merge(self) -> list[tuple[float, float]]:
247+
def get_ballooning_data(self) -> list[BallooningData]:
244248
ballooning_merge_curve = euklid.vector.Interpolation(self.ballooning_merge_curve.get_sequence(self.num_interpolate).nodes)
245-
factors = [ballooning_merge_curve.get_value(abs(x)) for x in self.shape.cell_x_values]
246-
247-
table = self.tables.ballooning_modifiers
249+
merge_factors = [ballooning_merge_curve.get_value(abs(x)) for x in self.shape.cell_x_values]
248250

249-
if table is not None:
250-
all_factors = table.get_merge_factors(factors)
251-
else:
252-
all_factors = [(factor, 1) for factor in factors]
251+
result = []
253252

254-
return [(max(0, x), y) for x,y in all_factors]
253+
for rib_no, merge_factor in enumerate(merge_factors):
254+
modifier = self.tables.ballooning_modifiers.get_ballooning_data(rib_no, self.resolvers)
255+
if modifier.merge_factor is None:
256+
modifier.merge_factor = merge_factor
257+
result.append(modifier)
258+
259+
return result
255260

256261
def apply_shape_and_arc(self, glider: Glider) -> None:
257262
x_values = [abs(x) for x in self.shape.rib_x_values]
@@ -455,17 +460,19 @@ def get_glider_3d(self, glider: Glider=None, num: int=50, num_profile: int | Non
455460

456461
logger.info("create cells")
457462

458-
ballooning_factors = self.get_ballooning_merge()
463+
ballooning_data = self.get_ballooning_data()
459464
glider.cells = []
460465
for cell_no, (rib1, rib2) in enumerate(zip(ribs[:-1], ribs[1:])):
461466

462-
ballooning_factor = ballooning_factors[cell_no]
463-
ballooning = self.merge_ballooning(*ballooning_factor)
467+
ballooning_factor = ballooning_data[cell_no]
468+
assert ballooning_factor.merge_factor is not None
469+
ballooning = self.merge_ballooning(ballooning_factor.merge_factor, ballooning_factor.ballooning_factor)
464470

465471
cell = Cell(
466472
rib1=rib1,
467473
rib2=rib2,
468474
ballooning=ballooning,
475+
ballooning_reference=ballooning_factor.ballooning_reference,
469476
name=f"c{cell_no+1}",
470477
attachment_points=[],
471478
ballooning_modifiers=self.tables.ballooning_modifiers.get_modifiers(cell_no, resolvers=resolvers)
@@ -517,15 +524,16 @@ def apply_ballooning(self, glider3d: Glider | None=None) -> list[BallooningBase]
517524
for ballooning in self.balloonings:
518525
ballooning.apply_splines()
519526

520-
ballooning_factors = self.get_ballooning_merge()
527+
ballooning_factors = self.get_ballooning_data()
521528
balloonings = []
522529

523-
for cell_no in range(len(ballooning_factors)):
524-
ballooning_factor = ballooning_factors[cell_no]
525-
ballooning = self.merge_ballooning(*ballooning_factor)
530+
for cell_no, ballooning_factor in enumerate(ballooning_factors):
531+
assert ballooning_factor.merge_factor is not None
532+
ballooning = self.merge_ballooning(ballooning_factor.merge_factor, ballooning_factor.ballooning_factor)
526533
if glider3d is not None:
527534
cell = glider3d.cells[cell_no]
528535
cell.ballooning = ballooning
536+
cell.ballooning_reference = ballooning_factor.ballooning_reference
529537

530538
balloonings.append(ballooning)
531539

openglider/glider/parametric/shape.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,13 @@ def get_half_shape(self, zrot: list[Angle | None] | None = None) -> Shape:
171171
return base_shape
172172

173173
baseline = base_shape.get_baseline(self.config.baseline_pct).nodes
174-
front_new = []
175-
back_new = []
174+
front_new: list[euklid.vector.Vector2D] = []
175+
back_new: list[euklid.vector.Vector2D] = []
176176

177177
for rib_no, angle in enumerate(zrot):
178178
if angle is None:
179-
front_new.append(front[rib_no])
180-
back_new.append(back[rib_no])
179+
front_new.append(euklid.vector.Vector2D(front[rib_no]))
180+
back_new.append(euklid.vector.Vector2D(back[rib_no]))
181181
else:
182182
rotation = euklid.vector.Rotation2D(angle.si)
183183
front_new.append(

openglider/glider/parametric/table/base/table.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def _prepare_dto_data(self, row: int, dto: type[DTO], data: list[Any], resolvers
151151
)
152152
index = index + 1 + max(tuple_type.index_offset)
153153
else:
154-
if field.annotation == str:
154+
if field.annotation is str or typing.get_origin(field.annotation) == typing.Literal:
155155
dct[field_name] = data[index]
156156
else:
157157
dct[field_name] = resolvers[row].parse(data[index])

openglider/glider/parametric/table/cell/ballooning.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import logging
2+
from typing import Literal, Self
23

4+
from openglider.glider.cell.cell import Cell
35
from openglider.glider.parametric.table.base import CellTable, Keyword
46
from openglider.glider.parametric.table.base.dto import DTO
57
from openglider.glider.parametric.table.base.parser import Parser
@@ -17,6 +19,14 @@ class BallooningRampDTO(DTO):
1719

1820
def get_object(self) -> EntryRamp:
1921
return EntryRamp(ramp_distance=self.ramp_distance)
22+
23+
class BallooningData(DTO):
24+
ballooning_reference: Literal["local", "cell"]
25+
merge_factor: float | None
26+
ballooning_factor: float | None
27+
28+
def get_object(self)-> Self:
29+
return self
2030

2131

2232
class BallooningModifierTable(CellTable):
@@ -26,8 +36,26 @@ class BallooningModifierTable(CellTable):
2636
}
2737
dtos = {
2838
"BallooningRamp": BallooningRampDTO,
39+
"BallooningModifier": BallooningData
2940
}
3041

42+
def get_ballooning_data(self, row: int, resolvers: list[Parser]) -> BallooningData:
43+
value: BallooningData | None = self.get_one(row_no=row, keywords=["BallooningModifier"], resolvers=resolvers)
44+
if value is None:
45+
value = BallooningData(ballooning_reference="local", merge_factor=None, ballooning_factor=None)
46+
47+
if value.merge_factor is None:
48+
merge_factors = self.get(row_no=row, keywords=["BallooningMerge"], resolvers=resolvers)
49+
if merge_factors:
50+
value.merge_factor = merge_factors[-1]["merge_factor"]
51+
52+
if value.ballooning_factor is None:
53+
ballooning_factors = self.get(row_no=row, keywords=["BallooningFactor"], resolvers=resolvers)
54+
if ballooning_factors:
55+
value.ballooning_factor = ballooning_factors[-1]["amount_factor"]
56+
57+
return value
58+
3159
def get_merge_factors(self, factor_list: list[float]) -> list[tuple[float, float]]:
3260

3361
merge_factors = factor_list[:]

openglider/glider/parametric/table/cell/miniribs.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from typing import Any
21
from openglider.glider.parametric.table.base.dto import DTO
32
from openglider.glider.rib import MiniRib
43

@@ -9,7 +8,7 @@ class MiniRibDTO(DTO):
98
y_value: Percentage
109
front_cut: Percentage
1110
back_cut: Percentage
12-
trailing_edge_cut: Length | Percentage
11+
trailing_edge_cut: Length
1312
material_code: str
1413

1514
def get_object(self) -> MiniRib:

0 commit comments

Comments
 (0)