diff --git a/nhf/build.py b/nhf/build.py index 05e4aaa..3e6ece3 100644 --- a/nhf/build.py +++ b/nhf/build.py @@ -90,7 +90,7 @@ class Target: x = ( Cq.Workplane() .add(x._faces) - .add(x._wires) + .add(x.wires) .add(x._edges) ) assert isinstance(x, Cq.Workplane) diff --git a/nhf/tool/__init__.py b/nhf/tool/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nhf/tool/light_panel.py b/nhf/tool/light_panel.py new file mode 100644 index 0000000..646165e --- /dev/null +++ b/nhf/tool/light_panel.py @@ -0,0 +1,146 @@ +from dataclasses import dataclass +import cadquery as Cq +from nhf import Material, Role +from nhf.build import Model, target, assembly, TargetKind, submodel +from nhf.parts.box import MountingBox, Hole +from nhf.parts.electronics import ArduinoUnoR3 +import nhf.utils + +@dataclass +class LightPanel(Model): + + # Dimensions of the base panel + length: float = 300.0 + width: float = 200.0 + + attach_height: float = 20.0 + attach_diam: float = 8.0 + attach_depth: float = 12.7 + + grid_height: float = 20.0 + grid_top_height: float = 5.0 + # Distance from grid to edge + grid_margin: float = 20.0 + # Number of holes in each row of the grid + grid_holes: int = 9 + grid_layers: int = 6 + grid_hole_width: float = 15.0 + + base_thickness: float = 25.4/16 + grid_thickness: float = 25.4/4 + base_material: Material = Material.WOOD_BIRCH + grid_material: Material = Material.ACRYLIC_TRANSPARENT + + controller: ArduinoUnoR3 = ArduinoUnoR3() + + def __post_init__(self): + assert self.grid_holes >= 2 + super().__init__(name="light-panel") + + @property + def grid_spacing_y(self) -> float: + return (self.width - 2 * self.grid_margin - self.grid_thickness) / (self.grid_layers - 1) + + @target(name="grid", kind=TargetKind.DXF) + def grid_profile(self): + w = self.length - self.grid_margin * 2 + h = self.grid_height + self.grid_top_height + + # The width of one hole (w0) satisfies + # n * w0 + (n+1) t = w + # where t is the thickness of the edge + n = self.grid_holes + w0 = self.grid_hole_width + t = (w - n * w0) / (n + 1) + # The spacing is such that the first and last holes are a distance `margin` + # away from the edges, so it satisfies + # t + w0/2 + (n-1) * s + w0/2 + t = w + step = (w - t*2 - w0) / (n - 1) + return ( + Cq.Sketch() + .push([(0, h/2)]) + .rect(w, h) + .push([ + (i * step + t + w0/2 - w/2, self.grid_height/2) + for i in range(0, n) + ]) + .rect(w0, self.grid_height, mode='s') + ) + + def grid(self) -> Cq.Workplane: + return ( + Cq.Workplane('XY') + .placeSketch(self.grid_profile()) + .extrude(self.grid_thickness) + ) + + @submodel(name="base") + def base(self) -> MountingBox: + xshift = self.length / 2 - self.controller.length - self.grid_margin / 2 + yshift = self.grid_margin / 2 + holes = [ + Hole( + x=x + xshift, y=y + yshift, + diam=self.controller.hole_diam, + tag=f"controller_conn{i}", + ) + for i, (x, y) in enumerate(self.controller.holes) + ] + return MountingBox( + holes=holes, + hole_diam=self.controller.hole_diam, + length=self.length, + width=self.width, + centred=(True, False), + thickness=self.base_thickness, + ) + + @target(name="attachment") + def attachment(self) -> Cq.Workplane: + l = self.length / 2 + w = self.width / 2 + return ( + Cq.Workplane('XY') + .box( + l, w, self.attach_height, + centered=(True, True, False), + ) + .faces(">Z") + .hole(self.attach_diam, self.attach_depth) + ) + + + def assembly(self) -> Cq.Assembly: + assembly = ( + Cq.Assembly() + .addS( + self.base().generate(), + name="base", + role=Role.STRUCTURE, + material=self.base_material, + ) + ) + # Grid thickness t is fixed, so the spacing of the grid satisfies + # margin + t + (n-1) * spacing + margin = width + spacing = self.grid_spacing_y + shift = self.grid_margin + self.grid_thickness / 2 + for i in range(self.grid_layers): + assembly = assembly.addS( + self.grid(), + name=f"grid_{i}", + role=Role.STRUCTURE, + material=self.grid_material, + loc=Cq.Location(0, spacing * i + shift, self.base_thickness, 90, 0, 0), + ) + return assembly + + +if __name__ == '__main__': + import sys + + p = LightPanel() + print(p.grid_spacing_y) + + if len(sys.argv) == 1: + p.build_all() + sys.exit(0)