Cosplay/nhf/parts/box.py

147 lines
4.9 KiB
Python

import cadquery as Cq
from dataclasses import dataclass, field
from typing import Tuple, Optional, Union, Callable
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
profile_callback: Optional[Callable[[Cq.Sketch], Cq.Sketch]] = None
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')
if self.profile_callback:
profile = self.profile_callback(profile)
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("<Y").workplane(origin=result.edges("<Y and >Z").val().Center()).tagPlane("left")
result.faces(">Y").workplane(origin=result.edges(">Y and >Z").val().Center()).tagPlane("right")
else:
result.faces("<Y").workplane(origin=result.vertices("<X and <Y and >Z").val().Center()).tagPlane("left")
result.faces(">Y").workplane(origin=result.vertices("<X and >Y and >Z").val().Center()).tagPlane("right")
c_y = ">Y" if self.flip_y else "<Y"
if self.centre_bot_top_tags:
result.faces("<X").workplane(origin=result.edges(f"<X and >Z").val().Center()).tagPlane("bot")
result.faces(">X").workplane(origin=result.edges(f">X and >Z").val().Center()).tagPlane("top")
else:
result.faces("<X").workplane(origin=result.vertices(f"<X and {c_y} and >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()