Cosplay/nhf/parts/box.py

157 lines
5.0 KiB
Python
Raw Normal View History

import cadquery as Cq
from dataclasses import dataclass, field
2024-07-25 12:35:36 -07:00
from typing import Tuple, Optional, Union, Callable
2024-07-16 15:42:39 -07:00
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
face: Optional[Cq.Face] = None
2024-07-22 15:02:26 -07:00
@property
def rev_tag(self) -> str:
assert self.tag is not None
return self.tag + "_rev"
def cutting_geometry(self, default_diam: Optional[float]=None) -> Cq.Face:
if self.face is not None:
return self.face
diam = self.diam if self.diam is not None else default_diam
assert diam is not None
return Cq.Face.makeFromWires(Cq.Wire.makeCircle(diam/2, Cq.Vector(), Cq.Vector(0,0,1)))
@dataclass
2024-07-16 15:42:39 -07:00
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
2024-07-19 16:13:33 -07:00
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
2024-07-23 16:49:25 -07:00
centre_bot_top_tags: bool = False
2024-07-23 19:13:06 -07:00
centre_left_right_tags: bool = False
2024-07-23 16:49:25 -07:00
# Determines the position of side tags
2024-07-16 17:18:28 -07:00
flip_y: bool = False
2024-07-25 12:35:36 -07:00
profile_callback: Optional[Callable[[Cq.Sketch], Cq.Sketch]] = None
2024-07-22 15:02:26 -07:00
def __post_init__(self):
for i, hole in enumerate(self.holes):
if hole.tag is None:
hole.tag = f"conn{i}"
2024-07-16 15:42:39 -07:00
@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:
face = hole.cutting_geometry(default_diam=self.hole_diam)
result.push([(hole.x, hole.y)]).each(lambda l:face.moved(l), mode='s')
2024-07-25 12:35:36 -07:00
if self.profile_callback:
result = self.profile_callback(result)
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'))
2024-07-22 15:02:26 -07:00
for hole in self.holes:
assert hole.tag
plane.moveTo(hole.x, hole.y).tagPlane(hole.tag)
if self.generate_reverse_tags:
2024-07-22 15:02:26 -07:00
reverse_plane.moveTo(hole.x, hole.y).tagPlane(hole.rev_tag, '-Z')
if self.generate_side_tags:
xn, xp = 0, self.length
if self.centred[0]:
xn -= self.length/2
xp -= self.length/2
yn, yp = 0, self.width
if self.centred[1]:
yn -= self.width/2
yp -= self.width/2
tag_x = xn + (self.length/2 if self.centre_left_right_tags else 0)
result.copyWorkplane(Cq.Workplane('XZ', origin=(tag_x, yn, self.thickness))).tagPlane("left")
result.copyWorkplane(Cq.Workplane('ZX', origin=(tag_x, yp, self.thickness))).tagPlane("right")
tag_y = yn + (self.width/2 if self.centre_bot_top_tags else 0)
result.copyWorkplane(Cq.Workplane('ZY', origin=(xn, tag_y, self.thickness))).tagPlane("bot")
result.copyWorkplane(Cq.Workplane('YZ', origin=(xp, tag_y, self.thickness))).tagPlane("top")
result.faces(">Z").tag("dir")
return result
def marked_assembly(self) -> Cq.Assembly:
result = (
Cq.Assembly()
.add(self.generate(), name="box")
)
2024-07-22 15:02:26 -07:00
for hole in self.holes:
result.markPlane(f"box?{hole.tag}")
if self.generate_reverse_tags:
2024-07-22 15:02:26 -07:00
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()