cosplay: Touhou/Houjuu Nue #4

Open
aniva wants to merge 189 commits from touhou/houjuu-nue into main
4 changed files with 67 additions and 21 deletions
Showing only changes of commit 7371333a84 - Show all commits

32
nhf/geometry.py Normal file
View File

@ -0,0 +1,32 @@
"""
Geometry functions
"""
from typing import Tuple
import math
def contraction_actuator_span_pos(
d_open: float,
d_closed: float,
theta: float,
) -> Tuple[float, float]:
"""
Calculates the position of the two ends of an actuator, whose fully opened
length is `d_open`, closed length is `d_closed`, and whose motion spans a
range `theta` (in radians). Returns (r, phi): If one end of the actuator is
held at `(r, 0)`, then the other end will trace an arc `r` away from the
origin with span `theta`
Let `P` (resp. `P'`) be the position of the front of the actuator when its
fully open (resp. closed), `Q` be the position of the back of the actuator,
we note that `OP = OP' = OQ`.
"""
pq2 = d_open * d_open
p_q2 = d_closed * d_closed
# angle of PQP'
psi = 0.5 * theta
# |P-P'|, via the triangle PQP'
pp_2 = pq2 + p_q2 - 2 * d_open * d_closed * math.cos(psi)
r2 = pp_2 / (2 - 2 * math.cos(theta))
# Law of cosines on POQ:
phi = math.acos(1 - pq2 / 2 / r2)
return math.sqrt(r2), phi

View File

@ -2,11 +2,13 @@
Unit tests for tooling
"""
from dataclasses import dataclass
import math
import unittest
import cadquery as Cq
from nhf.build import Model, target
from nhf.parts.item import Item
import nhf.checks
import nhf.geometry
import nhf.utils
# Color presets for testing purposes
@ -76,6 +78,25 @@ class TestChecks(unittest.TestCase):
with self.subTest(offset=offset):
self.intersect_test_case(offset)
class TestGeometry(unittest.TestCase):
def test_actuator_arm_pos(self):
do = 70.0
dc = 40.0
theta = math.radians(35.0)
r, phi = nhf.geometry.contraction_actuator_span_pos(do, dc, theta)
with self.subTest(state='open'):
x = r * math.cos(phi)
y = r * math.sin(phi)
d = math.sqrt((x - r) ** 2 + y ** 2)
self.assertAlmostEqual(d, do)
with self.subTest(state='closed'):
x = r * math.cos(phi - theta)
y = r * math.sin(phi - theta)
d = math.sqrt((x - r) ** 2 + y ** 2)
self.assertAlmostEqual(d, dc)
class TestUtils(unittest.TestCase):
def test_2d_orientation(self):

View File

@ -9,7 +9,6 @@ from nhf.materials import Role
from nhf.parts.item import Item
from nhf.parts.fasteners import FlatHeadBolt, HexNut
import nhf.utils
import scipy.optimize as SO
@dataclass(frozen=True)
class LinearActuator(Item):
@ -333,22 +332,6 @@ class Flexor:
bracket: MountingBracket = LINEAR_ACTUATOR_BRACKET
# FIXME: Add a compression spring so the serviceable distances are not as fixed
mount_loc_r: float = float('nan')
mount_loc_angle: float = float('nan')
def __post_init__(self):
d_open = self.actuator.conn_length + self.actuator.stroke_length
d_closed = self.actuator.conn_length
theta = math.radians(self.motion_span)
def target(args):
r, phi = args
e1 = d_open * d_open - 2 * r * r * (1 - math.cos(theta + phi))
e2 = d_closed * d_closed - 2 * r * r * (1 - math.cos(phi))
return [e1, e2]
self.mount_loc_r, phi = SO.fsolve(target, [self.actuator.conn_length, theta])
self.mount_loc_angle = math.degrees(theta + phi)
@property
def mount_height(self):
@ -360,13 +343,21 @@ class Flexor:
def max_serviceable_distance(self):
return self.min_serviceable_distance + self.actuator.stroke_length
def open_pos(self) -> Tuple[float, float]:
r, phi = nhf.geometry.contraction_actuator_span_pos(
d_open=self.actuator.conn_length + self.actuator.stroke_length,
d_closed=self.actuator.conn_length,
theta=math.radians(self.motion_span)
)
return r, math.degrees(phi)
def target_length_at_angle(
self,
angle: float = 0.0
) -> float:
# law of cosines
r = self.mount_loc_r
th = math.radians(self.mount_loc_angle - angle)
r, phi = self.open_pos()
th = math.radians(phi - angle)
d2 = 2 * r * r * (1 - math.cos(th))
return math.sqrt(d2)

View File

@ -9,6 +9,7 @@ from nhf.parts.fasteners import FlatHeadBolt, HexNut, ThreaddedKnob
from nhf.parts.joints import TorsionJoint, HirthJoint
from nhf.parts.box import Hole, MountingBox, box_with_centre_holes
from nhf.touhou.houjuu_nue.electronics import Flexor, LinearActuator
import nhf.geometry
import nhf.utils
TOL = 1e-6
@ -941,8 +942,9 @@ class ElbowJoint(Model):
loc_mount = Cq.Location.from2d(self.flexor.mount_height, 0) * Cq.Location.rot2d(180)
loc_mount_orient = Cq.Location.rot2d(self.flexor_mount_rot * (-1 if child else 1))
# Moves the hole to be some distance apart from 0
loc_span = Cq.Location.from2d(self.flexor.mount_loc_r, 0)
r = (-self.flexor.mount_loc_angle if child else 0) + 180
mount_r, mount_loc_angle = self.flexor.open_pos()
loc_span = Cq.Location.from2d(mount_r, 0)
r = (-mount_loc_angle if child else 0) + 180
loc_rot = Cq.Location.rot2d(r + self.flexor_offset_angle)
return loc_rot * loc_span * loc_mount_orient * loc_mount * loc_thickness