Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions openglider/glider/cell/attachment_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
from typing import TYPE_CHECKING, Any

import euklid
from pydantic import Field
from openglider.glider.shape import Shape
from openglider.lines.node import Node, NODE_TYPE_ENUM
from openglider.utils import table
from openglider.vector.unit import Percentage
from openglider.vector.unit import Length, Percentage

if TYPE_CHECKING:
from openglider.glider.cell.cell import Cell
Expand All @@ -21,7 +22,7 @@ class CellAttachmentPoint(Node):
rib_pos: Percentage
node_type: NODE_TYPE_ENUM = Node.NODE_TYPE.UPPER
ballooned: bool=False
offset: float = 0.
offset: Length = Field(default_factory=Length.zero)

def __repr__(self) -> str:
return f"<Attachment point '{self.name}' ({self.rib_pos})>"
Expand Down
78 changes: 52 additions & 26 deletions openglider/glider/curve.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
from typing import Any
from typing import Any, ClassVar

import euklid
import enum

from openglider.glider.shape import Shape
from openglider.utils.cache import cached_property
from openglider.utils.dataclass import BaseModel
from openglider.vector.unit import Angle, Length, Percentage, Quantity

class FreeCurve:
class CurveBase(BaseModel):
unit: str | None = None
interpolation: euklid.vector.Interpolation
shape: Shape

def to_unit(self, value: float) -> Quantity | float:
if self.unit is None:
return value

if self.unit in Angle.unit_variants or self.unit == Angle.unit:
return Angle(value, unit=self.unit)
if self.unit in Length.unit_variants or self.unit == Length.unit:
return Length(value, unit=self.unit)
if self.unit in Percentage.unit_variants:
return Percentage(value, self.unit)

raise ValueError()

class FreeCurve(CurveBase):
def __init__(self, points: list[euklid.vector.Vector2D], shape: Shape):
self.shape = shape
self.interpolation = euklid.vector.Interpolation(points)
super().__init__(
shape = shape,
interpolation = euklid.vector.Interpolation(points)
)

@property
def controlpoints(self) -> list[euklid.vector.Vector2D]:
Expand Down Expand Up @@ -67,11 +89,12 @@ def to_controlpoints(self, points: list[euklid.vector.Vector2D]) -> list[euklid.
def points_2d(self) -> list[euklid.vector.Vector2D]:
return self.to_2d(self.interpolation.nodes)

def get(self, rib_no: int) -> float:
def get(self, rib_no: int) -> float | Quantity:
if rib_no == 0 and self.shape.has_center_cell:
rib_no = 1

return self.interpolation.get_value(rib_no)
value = self.interpolation.get_value(rib_no)
return self.to_unit(value)

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


class Curve:
upper = False
class Curve(CurveBase):
def __init__(self, points: list[euklid.vector.Vector2D], shape: Shape):
self.interpolation = euklid.vector.Interpolation(points)
self.shape = shape
super().__init__(
interpolation = euklid.vector.Interpolation(points),
shape=shape
)

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

def get(self, rib_no: int) -> float:
def get(self, rib_no: int) -> float | Quantity:
if rib_no == 0 and self.shape.has_center_cell:
rib_no = 1

y = self.interpolation.get_value(rib_no)

if self.upper:
y = -y

return y
return self.to_unit(y)

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

points = [self.shape.get_point(x, self.get(x)) for x in x_values_lst]
percentage_lst: list[Percentage | float] = []
for x in x_values_lst:
y = self.get(x)
if not isinstance(y, (Percentage, float)):
raise ValueError()

percentage_lst.append(y)

for p in percentage_lst:
if not isinstance(p, (Percentage, float)):
raise ValueError()

points = [self.shape.get_point(x, y) for x, y in zip(x_values_lst, percentage_lst)]

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


class ShapeCurve(Curve):

def get(self, rib_no: int) -> float:
def get(self, rib_no: int) -> float | Quantity:
if rib_no == 0 and self.shape.has_center_cell:
rib_no = 1

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

return results[0][1]
return self.to_unit(results[0][1])


class ShapeBSplineCurve(ShapeCurve):
curve_cls = euklid.spline.BSplineCurve

def __init__(self, points: list[euklid.vector.Vector2D], shape: Shape, curve_cls: Any=None):
if curve_cls is not None:
self.curve_cls = curve_cls

super().__init__(points, shape)
curve_cls: ClassVar[type] = euklid.spline.BSplineCurve

@cached_property('shape', 'interpolation')
def points_2d(self) -> euklid.vector.PolyLine2D:
Expand Down
3 changes: 2 additions & 1 deletion openglider/glider/parametric/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import re
from typing import Any, ClassVar, Self

from packaging.version import Version
import euklid
import pydantic

Expand Down Expand Up @@ -121,7 +122,7 @@ class ParametricGliderConfig(ConfigTable):

use_sag: bool = True

version: str = __version__
version: Version = Version(__version__)

@classmethod
def __from_json__(cls, **data: Any) -> Self:
Expand Down
5 changes: 2 additions & 3 deletions openglider/glider/parametric/export_ods.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,8 @@ def add_curve(name: str, curve: CurveType, column_no: int) -> None:
add_curve("rib_distribution", glider.shape.rib_distribution, 4)
add_curve("arc", glider.arc.curve, 6)
add_curve("aoa", glider.aoa, 8)
add_curve("zrot", glider.zrot, 10)
add_curve("ballooning_merge_curve", glider.ballooning_merge_curve, 12)
add_curve("profile_merge_curve", glider.profile_merge_curve, 14)
add_curve("ballooning_merge_curve", glider.ballooning_merge_curve, 10)
add_curve("profile_merge_curve", glider.profile_merge_curve, 12)

return table

Expand Down
15 changes: 7 additions & 8 deletions openglider/glider/parametric/glider.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from openglider.utils.distribution import Distribution
from openglider.utils.table import Table
from openglider.utils.types import CurveType, SymmetricCurveType
from openglider.vector.unit import Percentage
from openglider.vector.unit import Percentage, Quantity

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

num_interpolate: int=30
num_profile: int | None=None
Expand Down Expand Up @@ -278,8 +277,8 @@ def resolvers(self) -> list[Parser]:
parsers = []
curves=self.get_curves()

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

aoa_values = self.get_aoa()
zrot_int = euklid.vector.Interpolation(self.zrot.get_sequence(num).nodes)

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

rotation = self.tables.rib_modifiers.get_rotation(rib_no, resolvers=resolvers)

rib = Rib(
profile_2d=profile,
pos=startpoint,
chord=chord,
arcang=rib_angles[rib_no],
xrot=self.tables.rib_modifiers.get_xrot(rib_no),
xrot=rotation.x,
offset=self.tables.rib_modifiers.get_offset(rib_no, resolvers=resolvers),
glide=self.glide,
aoa_absolute=aoa_values[rib_no],
zrot=zrot_int.get_value(abs(x_value)),
zrot=rotation.z,
holes=this_rib_holes,
rigidfoils=this_rigid_foils,
name=f"rib{rib_no}",
Expand Down Expand Up @@ -568,5 +568,4 @@ def rescale(curve: CurveType) -> None:
rescale(self.ballooning_merge_curve)
rescale(self.profile_merge_curve)
rescale(self.aoa)
rescale(self.zrot)
self.arc.rescale(self.shape.rib_x_values)
16 changes: 8 additions & 8 deletions openglider/glider/parametric/import_ods.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import math
import numbers
from typing import TYPE_CHECKING
from packaging.version import Version

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

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

glider_tables = GliderTables()
glider_tables.curves = CurveTable(table_dct.get("Curves", None))
glider_tables.curves = CurveTable(table_dct.get("Curves", None), config.version)

glider_tables.cuts = CutTable(cell_sheet, migrate_header=migrate_header)
glider_tables.ballooning_modifiers = BallooningModifierTable(cell_sheet, migrate_header=migrate_header)
glider_tables.holes = HolesTable(rib_sheet, migrate_header=migrate_header)
Expand Down Expand Up @@ -121,7 +123,6 @@ class Geometry(BaseModel):
shape: ParametricShape
arc: ArcCurve
aoa: SymmetricCurveType
zrot: SymmetricCurveType
profile_merge_curve: SymmetricCurveType
ballooning_merge_curve: SymmetricCurveType

Expand All @@ -135,7 +136,6 @@ def get_geometry_explicit(sheet: Table, config: ParametricGliderConfig) -> Geome
arc = []
profile_merge = []
ballooning_merge = []
zrot = []

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

zrot.append([span, line[7] * math.pi / 180])

span_last = span

def symmetric_fit(data: list[list[float]], bspline: bool=True) -> SymmetricCurveType:
Expand Down Expand Up @@ -200,7 +198,6 @@ def symmetric_fit(data: list[list[float]], bspline: bool=True) -> SymmetricCurve
shape=parametric_shape,
arc=arc_curve,
aoa=symmetric_fit(aoa),
zrot=symmetric_fit(zrot),
profile_merge_curve=symmetric_fit(profile_merge, bspline=True),
ballooning_merge_curve=symmetric_fit(ballooning_merge, bspline=True)
)
Expand All @@ -214,13 +211,16 @@ def get_geometry_parametric(table: Table, cell_num: int, config: ParametricGlide
"rib_distribution": euklid.spline.BezierCurve,
"arc": euklid.spline.SymmetricBSplineCurve,
"aoa": euklid.spline.SymmetricBSplineCurve,
"zrot": euklid.spline.SymmetricBSplineCurve,
"profile_merge_curve": euklid.spline.SymmetricBSplineCurve,
"ballooning_merge_curve": euklid.spline.SymmetricBSplineCurve
}

for column in range(0, table.num_columns, 2):
key = table[0, column]
if key not in curve_types:
if key == "zrot":
continue
raise ValueError(f"Invalid curve: {key}")
points = []

if table[0, column+1] is not None:
Expand Down
6 changes: 4 additions & 2 deletions openglider/glider/parametric/table/base/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def default_resolver(key: str) -> float:

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

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

def parse(self, expression: str | float) -> Quantity | float:
def parse(self, expression: str | float) -> Quantity | float | None:
if expression is None:
return None
if isinstance(expression, (float, int)):
return float(expression)

Expand Down
3 changes: 2 additions & 1 deletion openglider/glider/parametric/table/base/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from openglider.glider.parametric.table.base.dto import DTO
from openglider.glider.parametric.table.base.parser import Parser
from openglider.utils.table import Table
from openglider.vector.unit import Quantity

from .keyword import Keyword

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

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

Expand Down
Loading
Loading