2022-03-01 17:49:04 -05:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
2022-03-01 17:44:35 -05:00
|
|
|
import cadquery as cq
|
|
|
|
from cadquery import exporters
|
|
|
|
|
|
|
|
import key_bittings
|
|
|
|
from key_bittings import BittingSpecification
|
|
|
|
|
|
|
|
kerf = .11
|
|
|
|
|
|
|
|
bitting_width = 5
|
|
|
|
|
|
|
|
text_size = 5
|
|
|
|
text_gap = 2
|
|
|
|
|
|
|
|
margins = 5
|
|
|
|
margin_radius = 2
|
|
|
|
|
|
|
|
|
|
|
|
def bitting(loc: cq.Location, name: str, depth: float):
|
|
|
|
# can only make text in a 3D workplane, so make it in 3D and extract the face
|
|
|
|
text = (
|
|
|
|
cq.Workplane()
|
|
|
|
.text(name, fontsize=text_size, distance=1, valign="top", halign="center")
|
|
|
|
.edges("<Z")
|
|
|
|
)
|
|
|
|
|
|
|
|
return (
|
|
|
|
cq.Sketch()
|
|
|
|
.push([(0, depth/2)]).rect(bitting_width, depth - kerf)
|
|
|
|
.push([(0, -text_gap)]).face(text.vals())
|
|
|
|
.moved(loc)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def decoder_card(spec: BittingSpecification):
|
|
|
|
result = (
|
|
|
|
cq.Sketch()
|
|
|
|
.rect(margins * 2 + bitting_width * len(spec.depths),
|
|
|
|
margins * 2 + text_size + text_gap + max(*spec.depths.values()))
|
|
|
|
.vertices().fillet(margin_radius).reset()
|
|
|
|
)
|
|
|
|
|
|
|
|
depths = iter(spec.depths.items())
|
|
|
|
|
|
|
|
result = (
|
|
|
|
result
|
|
|
|
.rarray(bitting_width, 0, len(spec.depths), 1)
|
|
|
|
.each(lambda loc: bitting(loc, *next(depths)), mode="s")
|
|
|
|
)
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
sketch = decoder_card(key_bittings.national_disc_tumbler)
|
|
|
|
|
|
|
|
# for preview/export purposes, a 3D object is more convenient
|
|
|
|
result = cq.Workplane("front").workplane().placeSketch(sketch).extrude(1)
|
|
|
|
exporters.export(result, "national_disc_tumbler_decoder.svg", opt={
|
|
|
|
"showAxes": False,
|
|
|
|
"projectionDir": (0, 0, 1),
|
|
|
|
"strokeColor": (255, 0, 0),
|
|
|
|
})
|