From 8efea1d038f56ad203760febc7591ad20b6a28fa Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Sun, 4 Aug 2024 01:03:28 -0700 Subject: [PATCH] feat: Add arduino uno form factor --- nhf/parts/box.py | 2 +- nhf/parts/electronics.py | 72 ++++++++++++++++++++++++++++ nhf/touhou/houjuu_nue/__init__.py | 2 + nhf/touhou/houjuu_nue/electronics.py | 47 +++++++++++++++++- nhf/touhou/houjuu_nue/joints.py | 2 +- nhf/touhou/houjuu_nue/wing.py | 6 ++- 6 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 nhf/parts/electronics.py diff --git a/nhf/parts/box.py b/nhf/parts/box.py index 7270d29..7376826 100644 --- a/nhf/parts/box.py +++ b/nhf/parts/box.py @@ -40,7 +40,7 @@ class Hole: assert self.tag is not None return self.tag + "_rev" - def cutting_geometry(self, default_diam: Optional[float]=None) -> Cq.Face: + def cutting_geometry(self, default_diam: Optional[float] = None) -> Cq.Face: if self.face is not None: return self.face diam = self.diam if self.diam is not None else default_diam diff --git a/nhf/parts/electronics.py b/nhf/parts/electronics.py new file mode 100644 index 0000000..b20e8e8 --- /dev/null +++ b/nhf/parts/electronics.py @@ -0,0 +1,72 @@ +from dataclasses import dataclass, field +from typing import Tuple +import cadquery as Cq +from nhf import Item, Role +from nhf.parts.box import Hole, MountingBox +import nhf.utils + +@dataclass(frozen=True) +class ArduinoUnoR3(Item): + # From datasheet + mass: float = 25.0 + length: float = 68.6 + width: float = 53.4 + + # with clearance + base_height: float = 5.0 + # aesthetic only for illustrating clearance + total_height: float = 50.0 + roof_height: float = 20.0 + + # This is labeled in mirrored coordinates from top down (i.e. unmirrored from bottom up) + holes: list[Tuple[float, float]] = field(default_factory=lambda: [ + (15.24, 2.54), + (13.74, 50.80), # x coordinate not labeled on schematic + (66.04, 17.78), + (66.04, 45.72), + ]) + hole_diam: float = 3.0 + + @property + def name(self) -> str: + return "Arduino Uno R3" + + @property + def role(self) -> Role: + return Role.ELECTRONIC + + def generate(self) -> Cq.Assembly: + sketch = ( + Cq.Sketch() + .polygon([ + (0,0), + (self.length, 0), + (self.length, self.width), + (0,self.width) + ]) + .push([(x, self.width - y) for x,y in self.holes]) + .circle(self.hole_diam / 2, mode='s') + ) + # pillar thickness + t = 3.0 + pillar_height = self.total_height - self.base_height + pillar = Cq.Solid.makeBox( + t, t, pillar_height + ) + roof = Cq.Solid.makeBox( + self.length, self.width, t + ) + result = ( + Cq.Workplane('XY') + .placeSketch(sketch) + .extrude(self.base_height) + .union(pillar.located(Cq.Location((0, 0, self.base_height)))) + .union(pillar.located(Cq.Location((self.length - t, 0, self.base_height)))) + .union(pillar.located(Cq.Location((self.length - t, self.width - t, self.base_height)))) + .union(pillar.located(Cq.Location((0, self.width - t, self.base_height)))) + .union(roof.located(Cq.Location((0, 0, self.total_height - t)))) + ) + plane = result.copyWorkplane(Cq.Workplane('XY')) + for i, (x, y) in enumerate(self.holes): + plane.moveTo(x, self.width - y).tagPlane(f"conn{i}", direction='-Z') + return result diff --git a/nhf/touhou/houjuu_nue/__init__.py b/nhf/touhou/houjuu_nue/__init__.py index a3a925c..e7d9887 100644 --- a/nhf/touhou/houjuu_nue/__init__.py +++ b/nhf/touhou/houjuu_nue/__init__.py @@ -37,6 +37,7 @@ import nhf.touhou.houjuu_nue.wing as MW import nhf.touhou.houjuu_nue.trident as MT import nhf.touhou.houjuu_nue.joints as MJ import nhf.touhou.houjuu_nue.harness as MH +import nhf.touhou.houjuu_nue.electronics as ME from nhf.parts.item import Item import nhf.utils @@ -67,6 +68,7 @@ class Parameters(Model): parent_substrate_cull_corners=(1,1,1,1), parent_substrate_cull_edges=(0,0,1,0), ), + electronic_board=ME.ElectronicBoardControl(), shoulder_angle_bias=WING_DEFLECT_EVEN, s0_top_hole=True, s0_bot_hole=True, diff --git a/nhf/touhou/houjuu_nue/electronics.py b/nhf/touhou/houjuu_nue/electronics.py index 79a351d..35e74d3 100644 --- a/nhf/touhou/houjuu_nue/electronics.py +++ b/nhf/touhou/houjuu_nue/electronics.py @@ -11,6 +11,7 @@ from nhf.parts.box import MountingBox, Hole from nhf.parts.fibre import tension_fibre from nhf.parts.item import Item from nhf.parts.fasteners import FlatHeadBolt, HexNut +from nhf.parts.electronics import ArduinoUnoR3 from nhf.touhou.houjuu_nue.common import NUT_COMMON, BOLT_COMMON import nhf.utils @@ -509,7 +510,6 @@ class ElectronicBoard(Model): def __post_init__(self): super().__init__(name=self.name) - @submodel(name="panel") def panel(self) -> MountingBox: return MountingBox( holes=self.mount_holes, @@ -528,7 +528,7 @@ class ElectronicBoard(Model): .addS(panel.generate(), name="panel", role=Role.ELECTRONIC | Role.STRUCTURE, material=self.material) ) - for hole in panel.holes: + for hole in self.mount_holes: spacer_name = f"{hole.tag}_spacer" bolt_name = f"{hole.tag}_bolt" ( @@ -548,6 +548,49 @@ class ElectronicBoard(Model): ) return result.solve() +@dataclass +class ElectronicBoardBattery(ElectronicBoard): + name: str = "electronic-board-battery" + + @submodel(name="panel") + def panel_out(self) -> MountingBox: + return self.panel() + +@dataclass +class ElectronicBoardControl(ElectronicBoard): + name: str = "electronic-board-control" + + controller_datum: Cq.Location = Cq.Location.from2d(-25,10, -90) + + controller: ArduinoUnoR3 = ArduinoUnoR3() + + def panel(self) -> MountingBox: + box = super().panel() + def transform(i, x, y): + pos = self.controller_datum * Cq.Location.from2d(x, self.controller.width - y) + x, y = pos.to2d_pos() + return Hole( + x=x, y=y, + diam=self.controller.hole_diam, + tag=f"controller_conn{i}", + ) + box.holes = box.holes.copy() + [ + transform(i, x, y) + for i, (x, y) in enumerate(self.controller.holes) + ] + return box + + @submodel(name="panel") + def panel_out(self) -> MountingBox: + return self.panel() + + def assembly(self) -> Cq.Assembly: + result = super().assembly() + result.add(self.controller.assembly(), name="controller") + for i in range(len(self.controller.holes)): + result.constrain(f"controller?conn{i}", f"panel?controller_conn{i}", "Plane") + return result.solve() + @dataclass(frozen=True) class LightStrip: diff --git a/nhf/touhou/houjuu_nue/joints.py b/nhf/touhou/houjuu_nue/joints.py index c58116d..40f8e02 100644 --- a/nhf/touhou/houjuu_nue/joints.py +++ b/nhf/touhou/houjuu_nue/joints.py @@ -455,7 +455,7 @@ class ShoulderJoint(Model): """ Position of the middle of the spool measured from the middle """ - return self.height / 2 - self.torsion_joint.total_height - self.spool_base_height / 2 + return 0 def parent_lip_loc(self, left: bool=True) -> Cq.Location: """ diff --git a/nhf/touhou/houjuu_nue/wing.py b/nhf/touhou/houjuu_nue/wing.py index 179593d..c72ebdf 100644 --- a/nhf/touhou/houjuu_nue/wing.py +++ b/nhf/touhou/houjuu_nue/wing.py @@ -19,6 +19,7 @@ from nhf.touhou.houjuu_nue.electronics import ( LINEAR_ACTUATOR_21, LINEAR_ACTUATOR_50, ElectronicBoard, + ElectronicBoardBattery, LightStrip, ELECTRONIC_MOUNT_HEXNUT, ) @@ -76,7 +77,7 @@ class WingProfile(Model): s0_top_hole: bool = False s0_bot_hole: bool = True - electronic_board: ElectronicBoard = field(default_factory=lambda: ElectronicBoard()) + electronic_board: ElectronicBoard = field(default_factory=lambda: ElectronicBoardBattery()) s1_thickness: float = 25.0 @@ -156,6 +157,9 @@ class WingProfile(Model): @submodel(name="wrist-joint") def submodel_wrist_joint(self) -> Model: return self.wrist_joint + @submodel(name="electronic-board") + def submodel_electronic_board(self) -> Model: + return self.electronic_board @property def root_height(self) -> float: