Introducing the pirouette FPV frame for GFPV
Signed-off-by: Arnaud Morin <arnaud.gfpv@mailops.fr>
This commit is contained in:
168
frame_params.py
Normal file
168
frame_params.py
Normal file
@@ -0,0 +1,168 @@
|
||||
# @author Arnaud Morin <arnaud.gfpv@mailops.fr>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
from math import cos, radians, sin, sqrt
|
||||
|
||||
# --- material ---------------------------------------------------------------
|
||||
|
||||
THICKNESS = 2.0
|
||||
CF_DENSITY = 1.55e-3 # g/mm3, for mass reporting
|
||||
|
||||
# --- joint fit --------------------------------------------------------------
|
||||
|
||||
FIT = 0.2 # a mortise is its tenon's length + FIT (0.1 per
|
||||
# side); the width is a snug match, no FIT added
|
||||
TBONE_D = 1.0 # T-bone relief circle = one cutter diameter
|
||||
TENON_L = 4.0 # every tenon in the frame is this long -- the arm's
|
||||
# tip block and the spar's own tenon both key off it
|
||||
|
||||
# --- overall layout ---------------------------------------------------------
|
||||
|
||||
SPAR_H = 4.0
|
||||
SPAR_OFFSET = 2.5 # spar centreline clears the motor axis by this much,
|
||||
# outboard -- feeds R_SPAR_LINE below. motor_base.py
|
||||
# and spar.py each hold their own matching copy of
|
||||
# this value now, not linked by import any more, so
|
||||
# keep them in step by hand
|
||||
|
||||
# clear gap between the two plates
|
||||
PLATE_GAP = 15.0
|
||||
|
||||
# --- computed layout --------------------------------------------------------
|
||||
# On a true X, motor to motor gap is the size of the prop
|
||||
MOTOR_GAP = 78.0
|
||||
# thank you pythagore
|
||||
MOTOR_DIAG = MOTOR_GAP * sqrt(2.0)
|
||||
|
||||
|
||||
MOTOR_ANGLES = (45.0, 135.0, 225.0, 315.0) # true-X, one per corner
|
||||
|
||||
# The plate's own dimensions below are absolute, not derived from MOTOR_DIAG
|
||||
# or anything else that grows with the frame. Sheet thicknesses can't scale
|
||||
# -- they're stock -- and the stack patterns and motor bolt square are
|
||||
# hardware, so the plate is sized by hardware that doesn't shrink with the
|
||||
# frame: the 25.5 stack square, and the arm mortises that have to clear its
|
||||
# bolt rim. A plate that scaled down with a 110 mm wheelbase once drove a
|
||||
# mortise 0.6 mm off the bolt holes -- these numbers are the smallest that
|
||||
# keep that wall, and they stay fixed regardless of frame size.
|
||||
|
||||
R_SPAR_LINE = MOTOR_GAP / 2.0 + SPAR_OFFSET # |x| or |y| of a spar centreline
|
||||
|
||||
# --- Z stack-up -------------------------------------------------------------
|
||||
# z = 0 is the underside of the bottom plate; everything below is measured
|
||||
# up from there.
|
||||
|
||||
Z_TOP = THICKNESS + PLATE_GAP # underside of the top plate
|
||||
Z_TOP_FACE = Z_TOP + THICKNESS
|
||||
ARM_HEIGHT = Z_TOP_FACE # 19.0, the arm's own full height
|
||||
Z_ARM_MID = ARM_HEIGHT / 2.0 # the arm's mid-plane
|
||||
|
||||
# the upper motor base is pinned flush with the top plate: its top face
|
||||
# sits exactly level with the top plate's top face, since every part
|
||||
# shares one THICKNESS -- the motor sits at the same level as the top
|
||||
# plate rather than buried lower in the stack
|
||||
Z_MOTOR_BASE = Z_TOP # underside of the upper base
|
||||
|
||||
# The lower base hangs SPAR_H below the upper one. SPAR_H is still
|
||||
# the input that sets the spar's clear span; it just no longer places the
|
||||
# lower base symmetrically about the arm's mid-plane -- with the upper base
|
||||
# now pinned high, the lower one floats further from the bottom plate than
|
||||
# it used to, held by the spar (the arm doesn't reach it at all).
|
||||
Z_MOTOR_BASE_BOT_FACE = Z_MOTOR_BASE - SPAR_H # top face of the lower base
|
||||
Z_MOTOR_BASE_BOT = Z_MOTOR_BASE_BOT_FACE - THICKNESS
|
||||
Z_SPAR_MID = Z_MOTOR_BASE - SPAR_H / 2.0 # body top = base underside
|
||||
|
||||
# --- plate ------------------------------------------------------------------
|
||||
# The plate's own outline, web relief, hole layout and corner-lobe geometry
|
||||
# are all plate.py's own baked-literal business now -- none of it is a
|
||||
# cross-part fact any more, so it does not live here. The one thing that
|
||||
# does: where the arm's root tenon meets the plate, because arm_placements()
|
||||
# below has to put the arm's local origin at the radius that lines up with
|
||||
# it.
|
||||
R_PLATE_MORTISE = 25.3 # arm root mortise centre, on the diagonal --
|
||||
# matches plate.py's own hardcoded mortise
|
||||
# radius; the two aren't linked by import any
|
||||
# more, so keep them in step by hand
|
||||
|
||||
# --- arm ----------------------------------------------------------------
|
||||
# The arm's own outline lives in arm.py now, as hardcoded literals. What
|
||||
# stays here is only what places the arm in the assembly.
|
||||
|
||||
ARM_ROOT_TAB_X = 5.8 # tab centre -- arm.py's root tenon is 1 mm
|
||||
# out from the root end, so this and
|
||||
# R_PLATE_MORTISE both moved 1 mm to match
|
||||
ARM_R_ROOT = R_PLATE_MORTISE - ARM_ROOT_TAB_X # radius of the arm's root
|
||||
# end face
|
||||
|
||||
|
||||
# --- placement helpers ------------------------------------------------------
|
||||
|
||||
def motor_positions():
|
||||
"""The four motor axes, true-X."""
|
||||
return [(MOTOR_DIAG/2 * cos(radians(a)), MOTOR_DIAG/2 * sin(radians(a)))
|
||||
for a in MOTOR_ANGLES]
|
||||
|
||||
|
||||
def plate_placements():
|
||||
"""(x, y, z, rot_z) for the bottom and the top plate -- one part, twice."""
|
||||
return [(0.0, 0.0, 0.0, 0.0), (0.0, 0.0, Z_TOP, 0.0)]
|
||||
|
||||
|
||||
def arm_placements():
|
||||
"""(x, y, z, rot_z). Rotate about world Z, then translate. The local
|
||||
origin is the root end face, on the arm's mid-plane."""
|
||||
return [(ARM_R_ROOT * cos(radians(a)), ARM_R_ROOT * sin(radians(a)),
|
||||
Z_ARM_MID, a) for a in MOTOR_ANGLES]
|
||||
|
||||
|
||||
def motor_placements():
|
||||
"""(x, y, z, rot_z, flip) -- EIGHT bases: one above and one below every
|
||||
corner, all the same part. rot_z puts local +Y radially outward.
|
||||
|
||||
The lower four are turned over. _loc flips about the part's own X axis,
|
||||
which would swing the arm mortise from -Y to +Y, so the flip is paired with
|
||||
an extra 180 deg of rot_z: together they mirror the part about its own Y
|
||||
axis instead, which leaves the arm mortise on -Y, swaps the two spar
|
||||
mortises for each other, and maps the outline onto itself (it is symmetric
|
||||
in x). After the X flip the part hangs in local -T..0, hence the +T on z.
|
||||
"""
|
||||
out = []
|
||||
for (x, y), a in zip(motor_positions(), MOTOR_ANGLES):
|
||||
out.append((x, y, Z_MOTOR_BASE, a - 90.0, 0.0))
|
||||
for (x, y), a in zip(motor_positions(), MOTOR_ANGLES):
|
||||
out.append((x, y, Z_MOTOR_BASE_BOT + THICKNESS, a + 90.0, 180.0))
|
||||
return out
|
||||
|
||||
|
||||
def spar_placements():
|
||||
"""(x, y, z, rot_z, flip) -- one per edge of the outer square.
|
||||
|
||||
flip is 0 or 180 deg about the part's OWN length axis, applied BEFORE
|
||||
rot_z. Both lap notches of the part are on its +Z edge, so the flips must
|
||||
ALTERNATE round the ring: every corner then has one notch facing up and its
|
||||
mate facing down. (Front/rear flipped together instead leaves two corners
|
||||
correct and drives two tongues into each other at the other two.)
|
||||
"""
|
||||
d = R_SPAR_LINE
|
||||
return [
|
||||
(0.0, +d, Z_SPAR_MID, 0.0, 0.0), # front, notch up
|
||||
(+d, 0.0, Z_SPAR_MID, 90.0, 180.0), # right, notch down
|
||||
(0.0, -d, Z_SPAR_MID, 0.0, 0.0), # rear, notch up
|
||||
(-d, 0.0, Z_SPAR_MID, 90.0, 180.0), # left, notch down
|
||||
]
|
||||
|
||||
|
||||
# --- standoffs --------------------------------------------------------------
|
||||
# Four posts holding the plates apart. Their height is PLATE_GAP itself, so
|
||||
# they stand on the bottom plate's top face and meet the top plate's underside
|
||||
# exactly. The x/y pattern mirrors plate.py's own standoff holes -- not linked
|
||||
# by import, so keep the two in step by hand.
|
||||
STANDOFF_XY = (14.0, 20.0)
|
||||
|
||||
|
||||
def standoff_placements():
|
||||
"""(x, y, z, rot_z) -- one per corner of the standoff pattern."""
|
||||
x, y = STANDOFF_XY
|
||||
return [(sx * x, sy * y, THICKNESS, 0.0)
|
||||
for sx in (-1.0, 1.0) for sy in (-1.0, 1.0)]
|
||||
Reference in New Issue
Block a user