import cadquery as Cq from dataclasses import dataclass, field from typing import Tuple, Optional, Union from nhf.build import Model, TargetKind, target 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 @property def rev_tag(self) -> str: assert self.tag is not None return self.tag + "_rev" @dataclass class MountingBox(Model): """ 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_diam: Optional[float] = None centred: Tuple[bool, bool] = (False, True) generate_side_tags: bool = True # Generate tags on the opposite side generate_reverse_tags: bool = False centre_bot_top_tags: bool = False centre_left_right_tags: bool = False # Determines the position of side tags flip_y: bool = False def __post_init__(self): for i, hole in enumerate(self.holes): if hole.tag is None: hole.tag = f"conn{i}" @target(kind=TargetKind.DXF) 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) reverse_plane = result.copyWorkplane(Cq.Workplane('XY')) for hole in self.holes: assert hole.tag plane.moveTo(hole.x, hole.y).tagPlane(hole.tag) if self.generate_reverse_tags: reverse_plane.moveTo(hole.x, hole.y).tagPlane(hole.rev_tag, '-Z') if self.generate_side_tags: if self.centre_left_right_tags: result.faces("Z").val().Center()).tagPlane("left") result.faces(">Y").workplane(origin=result.edges(">Y and >Z").val().Center()).tagPlane("right") else: result.faces("Z").val().Center()).tagPlane("left") result.faces(">Y").workplane(origin=result.vertices("Y and >Z").val().Center()).tagPlane("right") c_y = ">Y" if self.flip_y else "Z").val().Center()).tagPlane("bot") result.faces(">X").workplane(origin=result.edges(f">X and >Z").val().Center()).tagPlane("top") else: result.faces("Z").val().Center()).tagPlane("bot") result.faces(">X").workplane(origin=result.vertices(f">X and {c_y} 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 hole in self.holes: result.markPlane(f"box?{hole.tag}") if self.generate_reverse_tags: result.markPlane(f"box?{hole.rev_tag}") if self.generate_side_tags: ( result .markPlane("box?left") .markPlane("box?right") .markPlane("box?dir") .markPlane("box?top") .markPlane("box?bot") ) return result.solve()