2024-08-04 01:03:28 -07:00
|
|
|
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),
|
2024-08-04 10:38:50 -07:00
|
|
|
(15.24 - 1.270, 50.80), # x coordinate not labeled on schematic
|
2024-08-04 01:03:28 -07:00
|
|
|
(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
|
2024-08-04 10:38:50 -07:00
|
|
|
|
|
|
|
|
|
|
|
@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,
|
|
|
|
)
|
|
|
|
)
|