Skip to content

Commit

Permalink
feat: command to generate rpi hat outline
Browse files Browse the repository at this point in the history
  • Loading branch information
sethfischer committed Feb 24, 2024
1 parent b2acaaa commit 702374d
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 0 deletions.
36 changes: 36 additions & 0 deletions src/osr_mechanical/console/application.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
"""Rover console command."""

import importlib
import logging
import tempfile
from argparse import ArgumentParser, Namespace
from base64 import b64encode
from datetime import datetime
from os import EX_OK, getcwd
from pathlib import Path
from sys import stdout

from cadquery import exporters as cq_exporters
from jinja2 import Environment, PackageLoader, select_autoescape

from osr_mechanical import __version__
Expand All @@ -21,6 +24,7 @@
from osr_mechanical.console.dxf import dxf_import_export
from osr_mechanical.console.exporters import ExportPNG
from osr_mechanical.console.release import ReleaseBuilder
from osr_mechanical.console.utilities import snake_to_camel_case

logging.basicConfig(encoding="utf-8", level=logging.INFO)
logger = logging.getLogger("osr_mechanical.console")
Expand Down Expand Up @@ -105,6 +109,27 @@ def export_bom(args: Namespace) -> None:
exit(EX_OK)


def export_pcb_outline(args: Namespace) -> None:
"""Export PCB outlines as DXF."""
module_name = (
f"osr_mechanical.pcb.{args.board}.{snake_to_camel_case(args.board)}Board"
)

module_name, class_name = module_name.rsplit(".", 1)
module = importlib.import_module(module_name)
container = getattr(module, class_name)

pcb = container().cq_object

with tempfile.TemporaryDirectory() as tmp_dir:
tmp_file = Path(tmp_dir) / "tmp.dxf"
cq_exporters.export(pcb, str(tmp_file), "DXF")

stdout.write(tmp_file.read_text())

exit(EX_OK)


def build_parser() -> ArgumentParser:
"""Parse arguments."""
parser = ArgumentParser(prog="console", description="Rover console command.")
Expand Down Expand Up @@ -207,6 +232,17 @@ def build_parser() -> ArgumentParser:
)
parser_bom.set_defaults(func=export_bom)

parser_pcb_outline = subparsers.add_parser(
"pcb-outline", help="generate printed circuit board outlines"
)
parser_pcb_outline.add_argument(
"--board",
required=True,
type=str,
help="board for which to generate outline",
)
parser_pcb_outline.set_defaults(func=export_pcb_outline)

return parser


Expand Down
6 changes: 6 additions & 0 deletions src/osr_mechanical/console/utilities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Utilities."""


def snake_to_camel_case(snake_str: str) -> str:
"""Convert a snake case string to camel case."""
return "".join(x.capitalize() for x in snake_str.lower().split("_"))
4 changes: 4 additions & 0 deletions src/osr_mechanical/pcb/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""PCB mechanical designs.
Mechanical designs for printed circuit boards such as board outlines.
"""
71 changes: 71 additions & 0 deletions src/osr_mechanical/pcb/rpi_hat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Raspberry Pi HAT mechanical design."""

import cadquery as cq

from osr_common.cq_containers import CqWorkplaneContainer


class RpiHatBoard(CqWorkplaneContainer):
"""Raspberry Pi HAT+ board outline.
See https://datasheets.raspberrypi.com/hat/hat-plus-specification.pdf
"""

def __init__(self) -> None:
"""Initialise RPi HAT+ board."""
self.width = 65
self.height = 56.5
self.thickness = 1.5
self.corner_radius = 3.5

self.csi_slot_width = 17
self.csi_slot_height = 2

self.dsi_slot_height = 17
self.dsi_slot_width = 5

self.mounting_hole_radius = 2.7 / 2
self.mounting_hole_between_centers_x = 58
self.mounting_hole_between_centers_y = 49

dsi_slot_loc_x = -self.width / 2 + self.dsi_slot_width / 2
dsi_slot_offset_y = -0.5
self.dsi_slot_loc = (dsi_slot_loc_x, dsi_slot_offset_y)

self._cq_object = self._make()

def outline(self) -> cq.Sketch:
"""Create RPi HAT+ board outline."""
sketch = (
cq.Sketch()
.rect(self.width, self.height)
.vertices()
.tag("major_outline")
.reset()
.push([(-(self.width / 2) + 50, -(self.height / 2) + 11.5)])
.slot(self.csi_slot_width, self.csi_slot_height, angle=90, mode="s")
.push([self.dsi_slot_loc])
.rect(self.dsi_slot_width, self.dsi_slot_height, mode="s")
.reset()
.vertices("(<X and <<Y[1]) or (<X and >>Y[2]) or (<<X[-2])")
.tag("dsi_slot_vertices")
.reset()
.rarray(
self.mounting_hole_between_centers_x,
self.mounting_hole_between_centers_y,
2,
2,
)
.circle(self.mounting_hole_radius, mode="s")
)

sketch.vertices(tag="major_outline").fillet(self.corner_radius)
sketch.vertices(tag="dsi_slot_vertices").fillet(1)

return sketch

def _make(self) -> cq.Workplane:
"""Create RPi HAT+ board."""
result = cq.Workplane("XY").placeSketch(self.outline()).extrude(self.thickness)

return result
1 change: 1 addition & 0 deletions tests/osr_mechanical/console/___init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Test console app utilities."""
13 changes: 13 additions & 0 deletions tests/osr_mechanical/console/test_utilities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Test console utilities."""

from osr_mechanical.console.utilities import snake_to_camel_case


class TestSnakeToCamelCase:
"""Test snake to camel case utility."""

def test_lower_snake_case(self) -> None:
"""Test valid snake case."""
result = snake_to_camel_case("valid_snake_case")

assert "ValidSnakeCase" == result

0 comments on commit 702374d

Please sign in to comment.