Slot-based connectors for crown side guards
This commit is contained in:
parent
0991b39d8a
commit
eb6343fa32
|
@ -3,6 +3,7 @@ from nhf.build import Model, target, assembly, TargetKind
|
|||
import nhf.utils
|
||||
|
||||
import math
|
||||
from typing import Optional
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
import cadquery as Cq
|
||||
|
@ -11,6 +12,8 @@ class AttachPoint(Enum):
|
|||
DOVETAIL_IN = 1
|
||||
DOVETAIL_OUT = 2
|
||||
NONE = 3
|
||||
# Inset slot for front surface attachment j
|
||||
SLOT = 4
|
||||
|
||||
@dataclass
|
||||
class Crown(Model):
|
||||
|
@ -20,9 +23,15 @@ class Crown(Model):
|
|||
base_circ: float = 538.0
|
||||
# Upper circumference, at the middle
|
||||
tilt_circ: float = 640.0
|
||||
front_base_circ: float = (640.0 + 538.0) / 2
|
||||
# Total height
|
||||
height: float = 120.0
|
||||
|
||||
# Front guard has a wing that inserts into the side guards.
|
||||
front_wing_angle: float = 9.0
|
||||
front_wing_dh: float = 40.0
|
||||
front_wing_height: float = 20.0
|
||||
|
||||
margin: float = 10.0
|
||||
|
||||
thickness: float = 0.4 # 26 Gauge
|
||||
|
@ -33,6 +42,13 @@ class Crown(Model):
|
|||
side_guard_hole_diam: float = 1.5
|
||||
side_guard_dovetail_height: float = 30.0
|
||||
|
||||
side_guard_slot_width: float = 22.0
|
||||
side_guard_slot_angle: float = 18.0
|
||||
# brass insert thickness
|
||||
slot_thickness: float = 2.0
|
||||
slot_width: float = 20.0
|
||||
slot_tilt: float = 45
|
||||
|
||||
material: Material = Material.METAL_BRASS
|
||||
material_side: Material = Material.PLASTIC_PLA
|
||||
|
||||
|
@ -43,6 +59,9 @@ class Crown(Model):
|
|||
assert self.facet_width_upper / 2 > self.height / 2, "Top angle must be > 90 degrees"
|
||||
assert self.side_guard_channel_radius > self.radius_lower
|
||||
|
||||
assert self.front_wing_angle < 180 / self.facets
|
||||
assert self.front_wing_dh + self.front_wing_height < self.height
|
||||
|
||||
@property
|
||||
def facet_width_lower(self):
|
||||
return self.base_circ / self.facets
|
||||
|
@ -59,6 +78,16 @@ class Crown(Model):
|
|||
def radius_upper(self):
|
||||
return (self.tilt_circ + (self.tilt_circ - self.base_circ)) / (2 * math.pi)
|
||||
|
||||
@property
|
||||
def radius_lower_front(self):
|
||||
return self.front_base_circ / (2 * math.pi)
|
||||
@property
|
||||
def radius_middle_front(self):
|
||||
return self.radius_lower_front + (self.radius_middle - self.radius_lower)
|
||||
@property
|
||||
def radius_upper_front(self):
|
||||
return self.radius_lower_front + (self.radius_upper - self.radius_lower)
|
||||
|
||||
def profile_base(self) -> Cq.Sketch:
|
||||
# Generate the pentagonal shape
|
||||
|
||||
|
@ -383,12 +412,42 @@ class Crown(Model):
|
|||
(0,0,dx + self.side_guard_dovetail_height),
|
||||
).moved((0, 0, -dx))
|
||||
|
||||
def side_guard(self, attach_left: AttachPoint, attach_right: AttachPoint) -> Cq.Workplane:
|
||||
def side_guard_frontal_slot(self) -> Cq.Workplane:
|
||||
angle = 360 / self.facets
|
||||
outer = Cq.Solid.makeCone(
|
||||
radius1=self.radius_lower_front + self.thickness,
|
||||
radius2=self.radius_upper_front + self.thickness,
|
||||
height=self.height,
|
||||
angleDegrees=angle,
|
||||
)
|
||||
inner = Cq.Solid.makeCone(
|
||||
radius1=self.radius_lower_front,
|
||||
radius2=self.radius_upper_front,
|
||||
height=self.height,
|
||||
angleDegrees=angle,
|
||||
)
|
||||
shell = (
|
||||
outer.cut(inner)
|
||||
.rotate((0,0,0), (0,0,1), -angle/2)
|
||||
)
|
||||
# Generate the sector intersector
|
||||
intersector = Cq.Solid.makeCylinder(
|
||||
radius=self.radius_upper + self.side_guard_thickness,
|
||||
height=self.front_wing_height,
|
||||
angleDegrees=self.front_wing_angle,
|
||||
).moved(Cq.Location(0,0,self.front_wing_dh,0,0,-self.front_wing_angle/2))
|
||||
return shell * intersector
|
||||
|
||||
def side_guard(
|
||||
self,
|
||||
attach_left: AttachPoint,
|
||||
attach_right: AttachPoint,
|
||||
) -> Cq.Workplane:
|
||||
"""
|
||||
Constructs the side guard using a cone. Via Gauss's Theorema Egregium,
|
||||
the surface of the cone can be deformed into a plane.
|
||||
"""
|
||||
angle_span = 360 / 5
|
||||
angle_span = 360 / self.facets
|
||||
outer = Cq.Solid.makeCone(
|
||||
radius1=self.radius_lower + self.side_guard_thickness,
|
||||
radius2=self.radius_upper + self.side_guard_thickness,
|
||||
|
@ -426,15 +485,20 @@ class Crown(Model):
|
|||
# )
|
||||
#)
|
||||
result = shell * profile# - channel
|
||||
for i in [-2, -1, 0, 1, 2]:
|
||||
phi = i * (math.pi / 14)
|
||||
hole = Cq.Solid.makeCylinder(
|
||||
radius=self.side_guard_hole_diam / 2,
|
||||
height=self.radius_upper * 2,
|
||||
pnt=(0, 0, self.side_guard_hole_height),
|
||||
dir=(math.cos(phi), math.sin(phi), 0),
|
||||
|
||||
# Create the slots
|
||||
for sign in [-1, 1]:
|
||||
slot = Cq.Solid.makeBox(
|
||||
length=self.height,
|
||||
width=self.slot_width,
|
||||
height=self.slot_thickness,
|
||||
).moved(
|
||||
Cq.Location.rot2d(sign * self.side_guard_slot_angle) *
|
||||
Cq.Location(self.radius_lower + self.side_guard_thickness/2, 0, 0) *
|
||||
Cq.Location(0,0,0,0,-180 + self.slot_tilt,0) *
|
||||
Cq.Location(-self.slot_thickness,-self.slot_width/2, -self.slot_thickness/2)
|
||||
)
|
||||
result = result - hole
|
||||
result = result - slot
|
||||
|
||||
radius_attach = self.radius_lower + self.side_guard_thickness / 2
|
||||
# tilt the dovetail by radius differential
|
||||
|
@ -443,12 +507,15 @@ class Crown(Model):
|
|||
loc_dovetail_left = Cq.Location.rot2d(angle_span / 2) * Cq.Location(radius_attach, 0, 0, 0, angle_tilt, 0)
|
||||
loc_dovetail_right = Cq.Location.rot2d(-angle_span / 2) * Cq.Location(radius_attach, 0, 0, 0, angle_tilt, 0)
|
||||
|
||||
angle_slot = 180 / self.facets - self.front_wing_angle / 2
|
||||
match attach_left:
|
||||
case AttachPoint.DOVETAIL_IN:
|
||||
loc_dovetail_left *= Cq.Location.rot2d(180)
|
||||
result = result - dovetail.moved(loc_dovetail_left)
|
||||
case AttachPoint.DOVETAIL_OUT:
|
||||
result = result + dovetail.moved(loc_dovetail_left)
|
||||
case AttachPoint.SLOT:
|
||||
result = result - self.side_guard_frontal_slot().moved(Cq.Location.rot2d(angle_slot))
|
||||
case AttachPoint.NONE:
|
||||
pass
|
||||
match attach_right:
|
||||
|
@ -457,6 +524,8 @@ class Crown(Model):
|
|||
case AttachPoint.DOVETAIL_OUT:
|
||||
loc_dovetail_right *= Cq.Location.rot2d(180)
|
||||
result = result + dovetail.moved(loc_dovetail_right)
|
||||
case AttachPoint.SLOT:
|
||||
result = result - self.side_guard_frontal_slot().moved(Cq.Location.rot2d(-angle_slot))
|
||||
case AttachPoint.NONE:
|
||||
pass
|
||||
# Remove parts below the horizontal
|
||||
|
@ -466,6 +535,12 @@ class Crown(Model):
|
|||
height=cut_h).moved((0,0,-cut_h))
|
||||
return result
|
||||
|
||||
@target(name="side_guard_1")
|
||||
def side_guard_1(self) -> Cq.Workplane:
|
||||
return self.side_guard(
|
||||
attach_left=AttachPoint.SLOT,
|
||||
attach_right=AttachPoint.DOVETAIL_IN,
|
||||
)
|
||||
@target(name="side_guard_2")
|
||||
def side_guard_2(self) -> Cq.Workplane:
|
||||
return self.side_guard(
|
||||
|
@ -475,9 +550,15 @@ class Crown(Model):
|
|||
@target(name="side_guard_3")
|
||||
def side_guard_3(self) -> Cq.Workplane:
|
||||
return self.side_guard(
|
||||
attach_left=AttachPoint.DOVETAIL_IN,
|
||||
attach_left=AttachPoint.DOVETAIL_OUT,
|
||||
attach_right=AttachPoint.DOVETAIL_IN,
|
||||
)
|
||||
@target(name="side_guard_3")
|
||||
def side_guard_4(self) -> Cq.Workplane:
|
||||
return self.side_guard(
|
||||
attach_left=AttachPoint.DOVETAIL_OUT,
|
||||
attach_right=AttachPoint.SLOT,
|
||||
)
|
||||
|
||||
def front_surrogate(self) -> Cq.Workplane:
|
||||
"""
|
||||
|
@ -486,14 +567,14 @@ class Crown(Model):
|
|||
"""
|
||||
angle = 360 / 5
|
||||
outer = Cq.Solid.makeCone(
|
||||
radius1=self.radius_lower + self.thickness,
|
||||
radius2=self.radius_upper + self.thickness,
|
||||
radius1=self.radius_lower_front + self.thickness,
|
||||
radius2=self.radius_upper_front + self.thickness,
|
||||
height=self.height,
|
||||
angleDegrees=angle,
|
||||
)
|
||||
inner = Cq.Solid.makeCone(
|
||||
radius1=self.radius_lower,
|
||||
radius2=self.radius_upper,
|
||||
radius1=self.radius_lower_front,
|
||||
radius2=self.radius_upper_front,
|
||||
height=self.height,
|
||||
angleDegrees=angle,
|
||||
)
|
||||
|
@ -501,7 +582,7 @@ class Crown(Model):
|
|||
outer.cut(inner)
|
||||
.rotate((0,0,0), (0,0,1), -angle/2)
|
||||
)
|
||||
dx = math.sin(math.radians(angle / 2)) * self.radius_middle
|
||||
dx = math.sin(math.radians(angle / 2)) * self.radius_middle_front
|
||||
profile = (
|
||||
Cq.Workplane('YZ')
|
||||
.polyline([
|
||||
|
@ -512,7 +593,7 @@ class Crown(Model):
|
|||
(dx, self.height / 2),
|
||||
])
|
||||
.close()
|
||||
.extrude(self.radius_upper + self.side_guard_thickness)
|
||||
.extrude(self.radius_upper_front + self.side_guard_thickness)
|
||||
.val()
|
||||
)
|
||||
return shell * profile
|
||||
|
@ -521,14 +602,20 @@ class Crown(Model):
|
|||
"""
|
||||
New assembly using conformal mapping on the cone.
|
||||
"""
|
||||
side_guard = self.side_guard_2()
|
||||
side_guards = [
|
||||
self.side_guard_1(),
|
||||
self.side_guard_2(),
|
||||
self.side_guard_3(),
|
||||
self.side_guard_4(),
|
||||
]
|
||||
a = Cq.Assembly()
|
||||
for i in range(1,5):
|
||||
for i,side_guard in enumerate(side_guards):
|
||||
angle = -(i+1) * 360 / self.facets
|
||||
a = a.addS(
|
||||
side_guard,
|
||||
name=f"side-{i}",
|
||||
material=self.material_side,
|
||||
loc=Cq.Location(rz=i*360/5)
|
||||
loc=Cq.Location(rz=angle)
|
||||
)
|
||||
a.addS(
|
||||
self.front_surrogate(),
|
||||
|
|
Loading…
Reference in New Issue