feat: Splined wing profile

This commit is contained in:
Leni Aniva 2024-07-11 16:02:54 -07:00
parent 2aeeaae061
commit d43c1944a7
Signed by: aniva
GPG Key ID: 4D9B1C8D10EA4C50
3 changed files with 156 additions and 32 deletions

View File

@ -548,7 +548,7 @@ class Parameters(Model):
@target(name="wing/r1s1", kind=TargetKind.DXF) @target(name="wing/r1s1", kind=TargetKind.DXF)
def wing_r1s1_profile(self) -> Cq.Sketch: def wing_r1s1_profile(self) -> Cq.Sketch:
return self.wing_profile.wing_r1_profile() return self.wing_profile.profile()
def wing_r1s1_panel(self, front=True) -> Cq.Workplane: def wing_r1s1_panel(self, front=True) -> Cq.Workplane:
profile = self.wing_r1s1_profile() profile = self.wing_r1s1_profile()

View File

@ -3,7 +3,9 @@ This file describes the shapes of the wing shells. The joints are defined in
`__init__.py`. `__init__.py`.
""" """
import math import math
from enum import Enum
from dataclasses import dataclass from dataclasses import dataclass
from typing import Mapping, Tuple
import cadquery as Cq import cadquery as Cq
from nhf import Material, Role from nhf import Material, Role
from nhf.parts.joints import HirthJoint from nhf.parts.joints import HirthJoint
@ -235,19 +237,19 @@ class WingProfile:
shoulder_height: float = 100 shoulder_height: float = 100
elbow_height: float = 120 elbow_height: float = 100
elbow_x: float = 270 elbow_x: float = 240
elbow_y: float = 10 elbow_y: float = 30
# Angle of elbow w.r.t. y axis # Tilt of elbow w.r.t. shoulder
elbow_angle: float = -20 elbow_angle: float = 20
wrist_height: float = 70 wrist_height: float = 70
# Bottom point of the wrist # Bottom point of the wrist
wrist_x: float = 400 wrist_x: float = 400
wrist_y: float = 200 wrist_y: float = 200
# Angle of wrist w.r.t. y axis. should be negative # Tile of wrist w.r.t. shoulder
wrist_angle: float = -40 wrist_angle: float = 40
# Extends from the wrist to the tip of the arrow # Extends from the wrist to the tip of the arrow
arrow_height: float = 300 arrow_height: float = 300
@ -261,21 +263,45 @@ class WingProfile:
def __post_init__(self): def __post_init__(self):
assert self.ring_radius > self.ring_radius_inner assert self.ring_radius > self.ring_radius_inner
self.elbow_theta = math.radians(self.elbow_angle)
self.elbow_c = math.cos(self.elbow_theta)
self.elbow_s = math.sin(self.elbow_theta)
self.elbow_top_x, self.elbow_top_y = self.elbow_to_abs(0, self.elbow_height)
self.wrist_theta = math.radians(self.wrist_angle)
self.wrist_c = math.cos(self.wrist_theta)
self.wrist_s = math.sin(self.wrist_theta)
self.wrist_top_x, self.wrist_top_y = self.wrist_to_abs(0, self.wrist_height)
self.arrow_theta = math.radians(self.arrow_angle)
self.arrow_x, self.arrow_y = self.wrist_to_abs(0, -self.arrow_height)
self.arrow_tip_x = self.arrow_x + (self.arrow_height + self.wrist_height) \
* math.sin(self.arrow_theta - self.wrist_theta)
self.arrow_tip_y = self.arrow_y + (self.arrow_height + self.wrist_height) \
* math.cos(self.arrow_theta - self.wrist_theta)
# [[c, s], [-s, c]] * [ring_x, ring_y]
self.ring_abs_x = self.wrist_top_x + self.wrist_c * self.ring_x - self.wrist_s * self.ring_y
self.ring_abs_y = self.wrist_top_y + self.wrist_s * self.ring_x + self.wrist_c * self.ring_y
@property @property
def ring_radius(self) -> float: def ring_radius(self) -> float:
dx = self.ring_x dx = self.ring_x
dy = self.ring_y dy = self.ring_y
return (dx * dx + dy * dy) ** 0.5 return (dx * dx + dy * dy) ** 0.5
def wing_r1_profile(self) -> Cq.Sketch: def elbow_to_abs(self, x: float, y: float) -> Tuple[float, float]:
wrist_theta = math.radians(self.wrist_angle) elbow_x = self.elbow_x + x * self.elbow_c - y * self.elbow_s
wrist_s = math.sin(wrist_theta) elbow_y = self.elbow_y + x * self.elbow_s + y * self.elbow_c
wrist_c = math.cos(wrist_theta) print(f"c={self.elbow_c}, s={self.elbow_s}, x={elbow_x}, y={elbow_y}")
wrist_top_x = self.wrist_x + self.wrist_height * wrist_s return elbow_x, elbow_y
wrist_top_y = self.wrist_y + self.wrist_height * wrist_c def wrist_to_abs(self, x: float, y: float) -> Tuple[float, float]:
elbow_theta = math.radians(self.elbow_angle) wrist_x = self.wrist_x + x * self.wrist_c - y * self.wrist_s
elbow_top_x = self.elbow_x + self.elbow_height * math.sin(elbow_theta) wrist_y = self.wrist_y + x * self.wrist_s + y * self.wrist_c
elbow_top_y = self.elbow_y + self.elbow_height * math.cos(elbow_theta) return wrist_x, wrist_y
def profile(self) -> Cq.Sketch:
"""
Net profile of the wing starting from the wing root with no divisions
"""
result = ( result = (
Cq.Sketch() Cq.Sketch()
.segment( .segment(
@ -284,8 +310,8 @@ class WingProfile:
tag="shoulder") tag="shoulder")
.arc( .arc(
(0, self.shoulder_height), (0, self.shoulder_height),
(elbow_top_x, elbow_top_y), (self.elbow_top_x, self.elbow_top_y),
(wrist_top_x, wrist_top_y), (self.wrist_top_x, self.wrist_top_y),
tag="s1_top") tag="s1_top")
#.segment( #.segment(
# (self.wrist_x, self.wrist_y), # (self.wrist_x, self.wrist_y),
@ -297,39 +323,108 @@ class WingProfile:
(self.wrist_x, self.wrist_y), (self.wrist_x, self.wrist_y),
tag="s1_bot") tag="s1_bot")
) )
arrow_theta = math.radians(self.arrow_angle)
arrow_x = self.wrist_x - self.arrow_height * wrist_s
arrow_y = self.wrist_y - self.arrow_height * wrist_c
arrow_tip_x = arrow_x + (self.arrow_height + self.wrist_height) * math.sin(arrow_theta + wrist_theta)
arrow_tip_y = arrow_y + (self.arrow_height + self.wrist_height) * math.cos(arrow_theta + wrist_theta)
result = ( result = (
result result
.segment( .segment(
(self.wrist_x, self.wrist_y), (self.wrist_x, self.wrist_y),
(arrow_x, arrow_y) (self.arrow_x, self.arrow_y)
) )
.segment( .segment(
(arrow_x, arrow_y), (self.arrow_x, self.arrow_y),
(arrow_tip_x, arrow_tip_y) (self.arrow_tip_x, self.arrow_tip_y)
) )
.segment( .segment(
(arrow_tip_x, arrow_tip_y), (self.arrow_tip_x, self.arrow_tip_y),
(wrist_top_x, wrist_top_y) (self.wrist_top_x, self.wrist_top_y)
) )
) )
# Carve out the ring # Carve out the ring
result = result.assemble() result = result.assemble()
ring_x = wrist_top_x + wrist_c * self.ring_x + wrist_s * self.ring_y
ring_y = wrist_top_y - wrist_s * self.ring_x + wrist_c * self.ring_y
result = ( result = (
result result
.push([(ring_x, ring_y)]) .push([(self.ring_abs_x, self.ring_abs_y)])
.circle(self.ring_radius, mode='a') .circle(self.ring_radius, mode='a')
.circle(self.ring_radius_inner, mode='s') .circle(self.ring_radius_inner, mode='s')
.clean() .clean()
) )
return result return result
def _mask_elbow(self) -> list[Tuple[float, float]]:
"""
Polygon shape to mask out parts above the elbow
"""
abscissa = 200
return [
(0, -abscissa),
(self.elbow_x, self.elbow_y),
(self.elbow_top_x, self.elbow_top_y),
(0, abscissa)
]
def _mask_wrist(self) -> list[Tuple[float, float]]:
abscissa = 200
return [
(0, -abscissa),
(self.wrist_x - self.wrist_s * abscissa,
self.wrist_y - self.wrist_c * abscissa),
(self.wrist_top_x, self.wrist_top_y),
(0, abscissa),
]
def profile_s1(self) -> Cq.Sketch:
profile = (
self.profile()
.reset()
.polygon(self._mask_elbow(), mode='i')
)
return profile
def surface_s1(self,
thickness:float = 25.4/16,
shoulder_mount_inset: float=20,
shoulder_joint_child_height: float=80,
elbow_mount_inset: float=20,
elbow_joint_parent_height: float=60,
front: bool=True) -> Cq.Workplane:
assert shoulder_joint_child_height < self.shoulder_height
assert elbow_joint_parent_height < self.elbow_height
h = (self.shoulder_height - shoulder_joint_child_height) / 2
tags_shoulder = [
("shoulder_bot", (shoulder_mount_inset, h), 90),
("shoulder_top", (shoulder_mount_inset, h + shoulder_joint_child_height), 270),
]
h = (self.elbow_height - elbow_joint_parent_height) / 2
tags_elbow = [
("elbow_bot",
self.elbow_to_abs(-elbow_mount_inset, h),
self.elbow_angle + 90),
("elbow_top",
self.elbow_to_abs(-elbow_mount_inset, h + elbow_joint_parent_height),
self.elbow_angle + 270),
]
profile = self.profile_s1()
tags = tags_shoulder + tags_elbow
return nhf.utils.extrude_with_markers(profile, thickness, tags, reverse=front)
def profile_s2(self) -> Cq.Sketch:
profile = (
self.profile()
.reset()
.polygon(self._mask_wrist(), mode='i')
.reset()
.polygon(self._mask_elbow(), mode='s')
)
return profile
def profile_s3(self) -> Cq.Sketch:
profile = (
self.profile()
.reset()
.polygon(self._mask_wrist(), mode='s')
)
return profile
def wing_r1s1_profile(self) -> Cq.Sketch: def wing_r1s1_profile(self) -> Cq.Sketch:
""" """
Generates the first wing segment profile, with the wing root pointing in Generates the first wing segment profile, with the wing root pointing in

View File

@ -5,6 +5,7 @@ Adds the functions to `Cq.Workplane`:
1. `tagPoint` 1. `tagPoint`
2. `tagPlane` 2. `tagPlane`
""" """
import math
import cadquery as Cq import cadquery as Cq
from typing import Union, Tuple from typing import Union, Tuple
@ -50,3 +51,31 @@ def tagPlane(self, tag: str,
self.eachpoint(edge.moved, useLocalCoordinates=True).tag(tag) self.eachpoint(edge.moved, useLocalCoordinates=True).tag(tag)
Cq.Workplane.tagPlane = tagPlane Cq.Workplane.tagPlane = tagPlane
def extrude_with_markers(sketch: Cq.Sketch,
thickness: float,
tags: list[Tuple[str, Tuple[float, float], float]],
reverse: bool = False):
"""
Extrudes a sketch and place tags on the sketch for mating.
Each tag is of the format `(name, (x, y), angle)`, where the angle is
specifies in degrees counterclockwise from +X. Two marks are generated for
each `name`, "{name}" for the location (with normal) and "{name}_dir" for
the directrix specified by the angle.
This simulates a process of laser cutting and bonding (for wood and acrylic)
"""
result = (
Cq.Workplane('XY')
.placeSketch(sketch)
.extrude(thickness)
)
plane = result.faces("<Z" if reverse else ">Z").workplane()
sign = -1 if reverse else 1
for tag, (px, py), angle in tag:
theta = sign * math.radians(angle)
direction = (math.cos(theta), math.sin(theta), 0)
plane.moveTo(px, sign * py).tagPlane(tag)
plane.moveTo(px, sign * py).tagPlane(f"{tag}_dir", direction)
return result