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), (15.24 - 1.270, 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 @dataclass(frozen=True) class BatteryBox18650(Item): """ A number of 18650 batteries in series """ mass: float = 17.4 + 68.80 * 3 length: float = 75.70 width_base: float = 61.46 - 18.48 - 20.18 * 2 battery_dist: float = 20.18 height: float = 19.66 # space from bottom to battery begin thickness: float = 1.66 battery_diam: float = 18.48 battery_height: float = 68.80 n_batteries: int = 3 def __post_init__(self): assert 2 * self.thickness < min(self.length, self.height) @property def name(self) -> str: return f"BatteryBox 18650*{self.n_batteries}" @property def role(self) -> Role: return Role.ELECTRONIC def generate(self) -> Cq.Workplane: width = self.width_base + self.battery_dist * (self.n_batteries - 1) + self.battery_diam return ( Cq.Workplane('XY') .box( length=self.length, width=width, height=self.height, centered=(True, True, False), ) .copyWorkplane(Cq.Workplane('XY', origin=(0, 0, self.thickness))) .box( length=self.length - self.thickness*2, width=width - self.thickness*2, height=self.height - self.thickness, centered=(True, True, False), combine='cut', ) .copyWorkplane(Cq.Workplane('XY', origin=(-self.battery_height/2, 0, self.thickness + self.battery_diam/2))) .rarray( xSpacing=1, ySpacing=self.battery_dist, xCount=1, yCount=self.n_batteries, center=True, ) .cylinder( radius=self.battery_diam/2, height=self.battery_height, direct=(1, 0, 0), centered=(True, True, False), combine=True, ) )