import cadquery as Cq from dataclasses import dataclass, field from typing import Tuple, Optional, Union import nhf.utils def box_with_centre_holes( length: float, width: float, height: float, hole_loc: list[float], hole_diam: float = 6.0, ) -> Cq.Workplane: """ Creates a box with holes along the X axis, marked `conn0, conn1, ...`. The box's y axis is centred """ result = ( Cq.Workplane('XY') .box(length, width, height, centered=(False, True, False)) .faces(">Z") .workplane() ) plane = result for i, x in enumerate(hole_loc): result = result.moveTo(x, 0).hole(hole_diam) plane.moveTo(x, 0).tagPlane(f"conn{i}") return result @dataclass class Hole: x: float y: float = 0.0 diam: Optional[float] = None tag: Optional[str] = None @dataclass class MountingBox: """ Create a box with marked holes """ length: float = 100.0 width: float = 60.0 thickness: float = 1.0 # List of (x, y), diam holes: list[Hole] = field(default_factory=lambda: [ Hole(x=5, y=5, diam=3), Hole(x=20, y=10, diam=5), ]) hole_diam: Optional[float] = None centred: Tuple[bool, bool] = (False, True) generate_side_tags: bool = True def profile(self) -> Cq.Sketch: bx, by = 0, 0 if not self.centred[0]: bx = self.length / 2 if not self.centred[1]: by = self.width / 2 result = ( Cq.Sketch() .push([(bx, by)]) .rect(self.length, self.width) ) for hole in self.holes: diam = hole.diam if hole.diam else self.hole_diam result.push([(hole.x, hole.y)]).circle(diam / 2, mode='s') return result def generate(self) -> Cq.Workplane: """ Creates box shape with markers """ result = ( Cq.Workplane('XY') .placeSketch(self.profile()) .extrude(self.thickness) ) plane = result.copyWorkplane(Cq.Workplane('XY')).workplane(offset=self.thickness) for i, hole in enumerate(self.holes): tag = hole.tag if hole.tag else f"conn{i}" plane.moveTo(hole.x, hole.y).tagPlane(tag) if self.generate_side_tags: result.faces("Z").val().Center()).tagPlane("left") result.faces(">Y").workplane(origin=result.vertices("Y and >Z").val().Center()).tagPlane("right") result.faces("Z").val().Center()).tagPlane("bot") result.faces(">X").workplane(origin=result.vertices(">X and Z").val().Center()).tagPlane("top") result.faces(">Z").tag("dir") return result def marked_assembly(self) -> Cq.Assembly: result = ( Cq.Assembly() .add(self.generate(), name="box") ) for i in range(len(self.holes)): result.markPlane(f"box?conn{i}") if self.generate_side_tags: ( result .markPlane("box?left") .markPlane("box?right") .markPlane("box?dir") .markPlane("box?top") .markPlane("box?bot") ) return result.solve()