Skip to content

Commit bc6aa45

Browse files
committed
add units to curves
1 parent 132d091 commit bc6aa45

File tree

16 files changed

+228
-98
lines changed

16 files changed

+228
-98
lines changed

openglider/glider/cell/attachment_point.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
from typing import TYPE_CHECKING, Any
55

66
import euklid
7+
from pydantic import Field
78
from openglider.glider.shape import Shape
89
from openglider.lines.node import Node, NODE_TYPE_ENUM
910
from openglider.utils import table
10-
from openglider.vector.unit import Percentage
11+
from openglider.vector.unit import Length, Percentage
1112

1213
if TYPE_CHECKING:
1314
from openglider.glider.cell.cell import Cell
@@ -21,7 +22,7 @@ class CellAttachmentPoint(Node):
2122
rib_pos: Percentage
2223
node_type: NODE_TYPE_ENUM = Node.NODE_TYPE.UPPER
2324
ballooned: bool=False
24-
offset: float = 0.
25+
offset: Length = Field(default_factory=Length.zero)
2526

2627
def __repr__(self) -> str:
2728
return f"<Attachment point '{self.name}' ({self.rib_pos})>"

openglider/glider/curve.py

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,37 @@
1-
from typing import Any
1+
from typing import Any, ClassVar
22

33
import euklid
44
import enum
55

66
from openglider.glider.shape import Shape
77
from openglider.utils.cache import cached_property
8+
from openglider.utils.dataclass import BaseModel
9+
from openglider.vector.unit import Angle, Length, Percentage, Quantity
810

9-
class FreeCurve:
11+
class CurveBase(BaseModel):
12+
unit: str | None = None
13+
interpolation: euklid.vector.Interpolation
14+
shape: Shape
15+
16+
def to_unit(self, value: float) -> Quantity | float:
17+
if self.unit is None:
18+
return value
19+
20+
if self.unit in Angle.unit_variants or self.unit == Angle.unit:
21+
return Angle(value, unit=self.unit)
22+
if self.unit in Length.unit_variants or self.unit == Length.unit:
23+
return Length(value, unit=self.unit)
24+
if self.unit in Percentage.unit_variants:
25+
return Percentage(value, self.unit)
26+
27+
raise ValueError()
28+
29+
class FreeCurve(CurveBase):
1030
def __init__(self, points: list[euklid.vector.Vector2D], shape: Shape):
11-
self.shape = shape
12-
self.interpolation = euklid.vector.Interpolation(points)
31+
super().__init__(
32+
shape = shape,
33+
interpolation = euklid.vector.Interpolation(points)
34+
)
1335

1436
@property
1537
def controlpoints(self) -> list[euklid.vector.Vector2D]:
@@ -67,11 +89,12 @@ def to_controlpoints(self, points: list[euklid.vector.Vector2D]) -> list[euklid.
6789
def points_2d(self) -> list[euklid.vector.Vector2D]:
6890
return self.to_2d(self.interpolation.nodes)
6991

70-
def get(self, rib_no: int) -> float:
92+
def get(self, rib_no: int) -> float | Quantity:
7193
if rib_no == 0 and self.shape.has_center_cell:
7294
rib_no = 1
7395

74-
return self.interpolation.get_value(rib_no)
96+
value = self.interpolation.get_value(rib_no)
97+
return self.to_unit(value)
7598

7699
def draw(self) -> euklid.vector.PolyLine2D:
77100
x_values = [p[0] for p in self.controlpoints]
@@ -92,11 +115,12 @@ def draw(self) -> euklid.vector.PolyLine2D:
92115
return euklid.vector.PolyLine2D(self.to_2d([euklid.vector.Vector2D([x, self.interpolation.get_value(x)]) for x in x_values_lst]))
93116

94117

95-
class Curve:
96-
upper = False
118+
class Curve(CurveBase):
97119
def __init__(self, points: list[euklid.vector.Vector2D], shape: Shape):
98-
self.interpolation = euklid.vector.Interpolation(points)
99-
self.shape = shape
120+
super().__init__(
121+
interpolation = euklid.vector.Interpolation(points),
122+
shape=shape
123+
)
100124

101125
@property
102126
def controlpoints(self) -> list[euklid.vector.Vector2D]:
@@ -154,16 +178,13 @@ def points_2d(self) -> euklid.vector.PolyLine2D:
154178
self.shape.get_point(*p) for p in self.interpolation.nodes
155179
])
156180

157-
def get(self, rib_no: int) -> float:
181+
def get(self, rib_no: int) -> float | Quantity:
158182
if rib_no == 0 and self.shape.has_center_cell:
159183
rib_no = 1
160184

161185
y = self.interpolation.get_value(rib_no)
162186

163-
if self.upper:
164-
y = -y
165-
166-
return y
187+
return self.to_unit(y)
167188

168189
def draw(self) -> euklid.vector.PolyLine2D:
169190
x_values = [p[0] for p in self.controlpoints]
@@ -181,7 +202,19 @@ def draw(self) -> euklid.vector.PolyLine2D:
181202
if end % 1:
182203
x_values_lst.append(end)
183204

184-
points = [self.shape.get_point(x, self.get(x)) for x in x_values_lst]
205+
percentage_lst: list[Percentage | float] = []
206+
for x in x_values_lst:
207+
y = self.get(x)
208+
if not isinstance(y, (Percentage, float)):
209+
raise ValueError()
210+
211+
percentage_lst.append(y)
212+
213+
for p in percentage_lst:
214+
if not isinstance(p, (Percentage, float)):
215+
raise ValueError()
216+
217+
points = [self.shape.get_point(x, y) for x, y in zip(x_values_lst, percentage_lst)]
185218

186219
if start == 1 and self.shape.has_center_cell:
187220
points.insert(0, points[0] * euklid.vector.Vector2D([-1,1]))
@@ -191,8 +224,7 @@ def draw(self) -> euklid.vector.PolyLine2D:
191224

192225

193226
class ShapeCurve(Curve):
194-
195-
def get(self, rib_no: int) -> float:
227+
def get(self, rib_no: int) -> float | Quantity:
196228
if rib_no == 0 and self.shape.has_center_cell:
197229
rib_no = 1
198230

@@ -203,17 +235,11 @@ def get(self, rib_no: int) -> float:
203235
if len(results) != 1:
204236
raise Exception(f"wrong number of cut results: {len(results)}")
205237

206-
return results[0][1]
238+
return self.to_unit(results[0][1])
207239

208240

209241
class ShapeBSplineCurve(ShapeCurve):
210-
curve_cls = euklid.spline.BSplineCurve
211-
212-
def __init__(self, points: list[euklid.vector.Vector2D], shape: Shape, curve_cls: Any=None):
213-
if curve_cls is not None:
214-
self.curve_cls = curve_cls
215-
216-
super().__init__(points, shape)
242+
curve_cls: ClassVar[type] = euklid.spline.BSplineCurve
217243

218244
@cached_property('shape', 'interpolation')
219245
def points_2d(self) -> euklid.vector.PolyLine2D:

openglider/glider/parametric/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import re
22
from typing import Any, ClassVar, Self
33

4+
from packaging.version import Version
45
import euklid
56
import pydantic
67

@@ -121,7 +122,7 @@ class ParametricGliderConfig(ConfigTable):
121122

122123
use_sag: bool = True
123124

124-
version: str = __version__
125+
version: Version = Version(__version__)
125126

126127
@classmethod
127128
def __from_json__(cls, **data: Any) -> Self:

openglider/glider/parametric/export_ods.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,8 @@ def add_curve(name: str, curve: CurveType, column_no: int) -> None:
185185
add_curve("rib_distribution", glider.shape.rib_distribution, 4)
186186
add_curve("arc", glider.arc.curve, 6)
187187
add_curve("aoa", glider.aoa, 8)
188-
add_curve("zrot", glider.zrot, 10)
189-
add_curve("ballooning_merge_curve", glider.ballooning_merge_curve, 12)
190-
add_curve("profile_merge_curve", glider.profile_merge_curve, 14)
188+
add_curve("ballooning_merge_curve", glider.ballooning_merge_curve, 10)
189+
add_curve("profile_merge_curve", glider.profile_merge_curve, 12)
191190

192191
return table
193192

openglider/glider/parametric/glider.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from openglider.utils.distribution import Distribution
2929
from openglider.utils.table import Table
3030
from openglider.utils.types import CurveType, SymmetricCurveType
31-
from openglider.vector.unit import Percentage
31+
from openglider.vector.unit import Percentage, Quantity
3232

3333
if TYPE_CHECKING:
3434
from openglider.glider.curve import GliderCurveType
@@ -53,7 +53,6 @@ class ParametricGlider:
5353
speed: float
5454
glide: float
5555
tables: GliderTables = Field(default_factory=lambda: GliderTables())
56-
zrot: SymmetricCurveType = Field(default_factory=lambda: euklid.spline.SymmetricBSplineCurve([[0,0],[1,0]]))
5756

5857
num_interpolate: int=30
5958
num_profile: int | None=None
@@ -278,8 +277,8 @@ def resolvers(self) -> list[Parser]:
278277
parsers = []
279278
curves=self.get_curves()
280279

281-
def resolver_factory(rib_no: int) -> Callable[[str], float]:
282-
def resolve(name: str) -> float:
280+
def resolver_factory(rib_no: int) -> Callable[[str], float | Quantity]:
281+
def resolve(name: str) -> float | Quantity:
283282
if name not in curves:
284283
raise ValueError(
285284
f"could not resolve name '{name}' "+
@@ -386,7 +385,6 @@ def get_glider_3d(self, glider: Glider=None, num: int=50, num_profile: int | Non
386385
shape_ribs = self.shape.ribs
387386

388387
aoa_values = self.get_aoa()
389-
zrot_int = euklid.vector.Interpolation(self.zrot.get_sequence(num).nodes)
390388

391389
arc_pos = self.arc.get_arc_positions(x_values).tolist()
392390
rib_angles = self.arc.get_rib_angles(x_values)
@@ -421,16 +419,18 @@ def get_glider_3d(self, glider: Glider=None, num: int=50, num_profile: int | Non
421419
}
422420
data.update(self.tables.rib_modifiers.get_rib_args(rib_no, resolvers=resolvers))
423421

422+
rotation = self.tables.rib_modifiers.get_rotation(rib_no, resolvers=resolvers)
423+
424424
rib = Rib(
425425
profile_2d=profile,
426426
pos=startpoint,
427427
chord=chord,
428428
arcang=rib_angles[rib_no],
429-
xrot=self.tables.rib_modifiers.get_xrot(rib_no),
429+
xrot=rotation.x,
430430
offset=self.tables.rib_modifiers.get_offset(rib_no, resolvers=resolvers),
431431
glide=self.glide,
432432
aoa_absolute=aoa_values[rib_no],
433-
zrot=zrot_int.get_value(abs(x_value)),
433+
zrot=rotation.z,
434434
holes=this_rib_holes,
435435
rigidfoils=this_rigid_foils,
436436
name=f"rib{rib_no}",
@@ -568,5 +568,4 @@ def rescale(curve: CurveType) -> None:
568568
rescale(self.ballooning_merge_curve)
569569
rescale(self.profile_merge_curve)
570570
rescale(self.aoa)
571-
rescale(self.zrot)
572571
self.arc.rescale(self.shape.rib_x_values)

openglider/glider/parametric/import_ods.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import math
55
import numbers
66
from typing import TYPE_CHECKING
7+
from packaging.version import Version
78

89
import euklid
910
import pyfoil
@@ -72,7 +73,7 @@ def import_ods_glider(cls: type[ParametricGlider], tables: list[Table]) -> Param
7273
# profiles = [BezierProfile2D(profile) for profile in transpose_columns(sheets[3])]
7374
profiles = [pyfoil.Airfoil(profile, name).normalized() for name, profile in transpose_columns(tables[3])]
7475

75-
if config.version > "0.0.1":
76+
if config.version > Version("0.0.1"):
7677
has_center_cell = not tables[0]["C2"] == 0
7778
cell_no = (tables[0].num_rows - 2) * 2 + has_center_cell
7879
geometry = get_geometry_parametric(table_dct[TableNames.parametric_data], cell_no, config)
@@ -88,7 +89,8 @@ def import_ods_glider(cls: type[ParametricGlider], tables: list[Table]) -> Param
8889
migrate_header = cell_sheet[0, 0] is not None and cell_sheet[0, 0] < "V4"
8990

9091
glider_tables = GliderTables()
91-
glider_tables.curves = CurveTable(table_dct.get("Curves", None))
92+
glider_tables.curves = CurveTable(table_dct.get("Curves", None), config.version)
93+
9294
glider_tables.cuts = CutTable(cell_sheet, migrate_header=migrate_header)
9395
glider_tables.ballooning_modifiers = BallooningModifierTable(cell_sheet, migrate_header=migrate_header)
9496
glider_tables.holes = HolesTable(rib_sheet, migrate_header=migrate_header)
@@ -121,7 +123,6 @@ class Geometry(BaseModel):
121123
shape: ParametricShape
122124
arc: ArcCurve
123125
aoa: SymmetricCurveType
124-
zrot: SymmetricCurveType
125126
profile_merge_curve: SymmetricCurveType
126127
ballooning_merge_curve: SymmetricCurveType
127128

@@ -135,7 +136,6 @@ def get_geometry_explicit(sheet: Table, config: ParametricGliderConfig) -> Geome
135136
arc = []
136137
profile_merge = []
137138
ballooning_merge = []
138-
zrot = []
139139

140140
y = z = span_last = alpha = 0.
141141
for i in range(1, sheet.num_rows):
@@ -162,8 +162,6 @@ def get_geometry_explicit(sheet: Table, config: ParametricGliderConfig) -> Geome
162162
profile_merge.append([span, line[8]])
163163
ballooning_merge.append([span, line[9]])
164164

165-
zrot.append([span, line[7] * math.pi / 180])
166-
167165
span_last = span
168166

169167
def symmetric_fit(data: list[list[float]], bspline: bool=True) -> SymmetricCurveType:
@@ -200,7 +198,6 @@ def symmetric_fit(data: list[list[float]], bspline: bool=True) -> SymmetricCurve
200198
shape=parametric_shape,
201199
arc=arc_curve,
202200
aoa=symmetric_fit(aoa),
203-
zrot=symmetric_fit(zrot),
204201
profile_merge_curve=symmetric_fit(profile_merge, bspline=True),
205202
ballooning_merge_curve=symmetric_fit(ballooning_merge, bspline=True)
206203
)
@@ -214,13 +211,16 @@ def get_geometry_parametric(table: Table, cell_num: int, config: ParametricGlide
214211
"rib_distribution": euklid.spline.BezierCurve,
215212
"arc": euklid.spline.SymmetricBSplineCurve,
216213
"aoa": euklid.spline.SymmetricBSplineCurve,
217-
"zrot": euklid.spline.SymmetricBSplineCurve,
218214
"profile_merge_curve": euklid.spline.SymmetricBSplineCurve,
219215
"ballooning_merge_curve": euklid.spline.SymmetricBSplineCurve
220216
}
221217

222218
for column in range(0, table.num_columns, 2):
223219
key = table[0, column]
220+
if key not in curve_types:
221+
if key == "zrot":
222+
continue
223+
raise ValueError(f"Invalid curve: {key}")
224224
points = []
225225

226226
if table[0, column+1] is not None:

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def default_resolver(key: str) -> float:
3030

3131
class Parser(BaseModel):
3232
units: list[type[Quantity]] = Field(default_factory=default_units.copy)
33-
variable_resolver: Callable[[str], float] = default_resolver
33+
variable_resolver: Callable[[str], float | Quantity] = default_resolver
3434

3535
_parser: Forward | None = pydantic.PrivateAttr(default=None)
3636
_units: dict[str, type[Quantity]] | None = pydantic.PrivateAttr(default=None)
@@ -141,7 +141,9 @@ def push_unary_minus(self, toks: ParseResults) -> None:
141141
else:
142142
break
143143

144-
def parse(self, expression: str | float) -> Quantity | float:
144+
def parse(self, expression: str | float) -> Quantity | float | None:
145+
if expression is None:
146+
return None
145147
if isinstance(expression, (float, int)):
146148
return float(expression)
147149

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from openglider.glider.parametric.table.base.dto import DTO
1010
from openglider.glider.parametric.table.base.parser import Parser
1111
from openglider.utils.table import Table
12+
from openglider.vector.unit import Quantity
1213

1314
from .keyword import Keyword
1415

@@ -110,7 +111,7 @@ def get(self, row_no: int, keywords: list[str] | None=None, **kwargs: Any) -> li
110111
return elements
111112

112113
@staticmethod
113-
def get_curve_value(curves: dict[str, GliderCurveType] | None, curve_name: str | float, rib_no: int) -> float:
114+
def get_curve_value(curves: dict[str, GliderCurveType] | None, curve_name: str | float, rib_no: int) -> float | Quantity:
114115
if curves is None:
115116
raise ValueError("No curves specified")
116117

0 commit comments

Comments
 (0)