ContentsFoundations to FrontierBezhig

eng-lab/labs/el01_hover/tests/test_arm.py

All lab code. Download this file.

"""EL01 lab tests for hwe/core/arm.py (step 6). These build real CAD, so they take a few seconds."""
import math
import unittest

from _impl import load
from hwe.units import Q_

arm_mod = load("arm")

WORKED = dict(length=Q_(100, "mm"), width=Q_(12, "mm"), thickness=Q_(5, "mm"), pad_size=Q_(28, "mm"),
              motor_hole_spacing=Q_(16, "mm"), motor_hole_diameter=Q_(3.2, "mm"),
              center_hole_diameter=Q_(8, "mm"), frame_hole_diameter=Q_(3.2, "mm"),
              frame_hole_inset=Q_(6, "mm"), frame_hole_pitch=Q_(16, "mm"))

OTHER = dict(length=Q_(0.13, "m"), width=Q_(1.4, "cm"), thickness=Q_(4, "mm"), pad_size=Q_(30, "mm"),
             motor_hole_spacing=Q_(19, "mm"), motor_hole_diameter=Q_(3.2, "mm"),
             center_hole_diameter=Q_(9, "mm"), frame_hole_diameter=Q_(3.2, "mm"),
             frame_hole_inset=Q_(7, "mm"), frame_hole_pitch=Q_(20, "mm"))


def hand_volume_mm3(p):
    """The test's own geometry, in plain millimetres (independent of your code)."""
    mm = {k: v.to("mm").m for k, v in p.items()}
    L, w, t, a = mm["length"], mm["width"], mm["thickness"], mm["pad_size"]
    area = L * w + a * a - (a / 2) * w
    holes = math.pi / 4 * (4 * mm["motor_hole_diameter"] ** 2 + mm["center_hole_diameter"] ** 2
                           + 2 * mm["frame_hole_diameter"] ** 2)
    return t * (area - holes)


class Step6Arm(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.worked = arm_mod.arm(**WORKED)
        cls.other = arm_mod.arm(**OTHER)

    def test_hand_volume_of_the_worked_arm(self):
        # 5 x (100x12 + 28x28 - 14x12 - pi/4 (4x3.2^2 + 8^2 + 2x3.2^2)) = 8587.398 mm^3
        v = arm_mod.arm_volume_by_hand(**WORKED).to("mm^3").m
        self.assertAlmostEqual(v, hand_volume_mm3(WORKED), delta=1e-6)
        self.assertAlmostEqual(v, 8587.398, delta=1e-3)

    def test_hand_volume_mixed_units(self):
        self.assertAlmostEqual(arm_mod.arm_volume_by_hand(**OTHER).to("mm^3").m, hand_volume_mm3(OTHER), delta=1e-6)

    def test_cad_volume_matches_hand_geometry(self):
        self.assertAlmostEqual(self.worked.volume, hand_volume_mm3(WORKED), delta=1e-6 * hand_volume_mm3(WORKED))

    def test_cad_volume_matches_with_other_units(self):
        # 0.13 m and 1.4 cm must reach build123d as 130 mm and 14 mm
        self.assertAlmostEqual(self.other.volume, hand_volume_mm3(OTHER), delta=1e-6 * hand_volume_mm3(OTHER))

    def test_bounding_box(self):
        # x from 0 to length + pad/2 = 114; y spans the pad, 28; z spans the thickness, 5 (mm)
        size = self.worked.bounding_box().size
        self.assertAlmostEqual(size.X, 114.0, delta=1e-6)
        self.assertAlmostEqual(size.Y, 28.0, delta=1e-6)
        self.assertAlmostEqual(size.Z, 5.0, delta=1e-6)

    def test_frame_end_starts_at_x_zero(self):
        box = self.worked.bounding_box()
        self.assertAlmostEqual(box.min.X, 0.0, delta=1e-6)
        self.assertAlmostEqual(box.min.Z, 0.0, delta=1e-6)

    def test_one_solid_with_seven_holes(self):
        self.assertEqual(len(self.worked.solids()), 1)
        holes = [f for f in self.worked.faces() if f.geom_type.name == "CYLINDER"]
        self.assertEqual(len(holes), 7)

    def test_motor_holes_are_on_the_pattern(self):
        # Each motor hole's axis is 8 mm (half of 16) from the motor centre (100, 0) in x and in y.
        # A hole's wall is a cylinder; the centre of its bounding box lies on the hole's axis.
        from build123d import CenterOf
        centres = []
        for f in self.worked.faces():
            if f.geom_type.name == "CYLINDER":
                c = f.center(CenterOf.BOUNDING_BOX)
                centres.append((round(c.X, 3), round(c.Y, 3)))
        for x, y in [(92.0, -8.0), (92.0, 8.0), (108.0, -8.0), (108.0, 8.0), (100.0, 0.0), (6.0, 0.0), (22.0, 0.0)]:
            self.assertIn((x, y), centres)


if __name__ == "__main__":
    unittest.main()