Component Parts¶
The composition of this fastener is relatively simple; we smiply have 2 parts grouped into a single mechanical fastener.
easy_fastener_1
├─○ wood_screw
└─○ anchor
And to demonstrate its function, these will be used to connect 2 wooden panels. So we’ll need the panels too.
Wood Screw¶
Wood screw will use MaleFastenerPart
as a basis.
- screw body : built from
MaleFastenerPart
, then modified. - shaft : we’ll add the bore-sized shaft (with some cylindrical cut-outs) to the neck of the screw.
import cadquery
import cqparts
from cqparts.params import *
from cqparts_fasteners.params import HeadType, DriveType, ThreadType
from cqparts_fasteners.male import MaleFastenerPart
from cqparts.display import display, render_props
from cqparts.constraint import Mate
from cqparts.utils import CoordSystem
class WoodScrew(MaleFastenerPart):
# --- override MaleFastenerPart parameters
# sub-parts
head = HeadType(default=('cheese', {
'diameter': 4,
'height': 2,
}), doc="screw's head")
drive = DriveType(default=('phillips', {
'diameter': 3.5,
'depth': 2.5,
'width': 0.5,
}), doc="screw's drive")
thread = ThreadType(default=('triangular', {
'diameter': 5, # outer
'diameter_core': 4.3, # inner
'pitch': 2,
'angle': 30,
}), doc="screw's thread")
# scalars
neck_diam = PositiveFloat(2, doc="neck diameter")
neck_length = PositiveFloat(40, doc="length from base of head to end of neck")
length = PositiveFloat(50, doc="length from base of head to end of thread")
# --- parameters unique for this class
neck_exposed = PositiveFloat(2, doc="length of neck exposed below head")
bore_diam = PositiveFloat(6, doc="diameter of screw's bore")
_render = render_props(template='aluminium')
def initialize_parameters(self):
super(WoodScrew, self).initialize_parameters()
def make(self):
screw = super(WoodScrew, self).make()
# add bore cylinder
bore = cadquery.Workplane('XY', origin=(0, 0, -self.neck_length)) \
.circle(self.bore_diam / 2) \
.extrude(self.neck_length - self.neck_exposed)
# cut out sides from bore so it takes less material
for angle in [i * (360 / 3) for i in range(3)]:
slice_obj = cadquery.Workplane(
'XY',
origin=(self.bore_diam / 2, 0, -(self.neck_exposed + 2))
).circle(self.bore_diam / 3) \
.extrude(-(self.neck_length - self.neck_exposed - 4)) \
.rotate((0,0,0), (0,0,1), angle)
bore = bore.cut(slice_obj)
screw = screw.union(bore)
return screw
def make_cutter(self):
# we won't use MaleFastenerPart.make_cutter() because it
# implements an access hole that we don't need.
cutter = cadquery.Workplane('XY', origin=(0, 0, self.head.height)) \
.circle(self.bore_diam / 2) \
.extrude(-(self.neck_length + self.head.height))
cutter = cutter.union(
self.thread.make_pilothole_cutter().translate((
0, 0, -self.length
))
)
return cutter
def make_simple(self):
# in this case, the cutter solid serves as a good simplified
# model of the screw.
return self.make_cutter()
@property
def mate_threadstart(self):
return Mate(self, CoordSystem(origin=(0, 0, -self.neck_length)))
So to illustrate what we’ve just made:
screw = Screw()
display(screw)
Anchor¶
The anchor is composed of:
- main body : the anchor will be cut from a large cylindrical block.
- neck slot : slot allowing woodscrew’s neck access.
- head slot : conical wedge to pull on screwhead when installed.
- access port : 1 quadrant cut out to to allow screwhead access.
- screw drive : to allow user to apply a screwdriver to install.
from math import sin, cos, pi
class Anchor(cqparts.Part):
# sub-parts
drive = DriveType(default=('cross', {
'diameter': 5,
'width': 1,
'depth': 2.5,
}), doc="anchor's drive")
# scalars
diameter = PositiveFloat(10, doc="diameter of anchor")
height = PositiveFloat(5, doc="height of anchor")
neck_diameter = PositiveFloat(2, doc="width of screw neck")
head_diameter = PositiveFloat(4, doc="width of screw head")
spline_point_count = IntRange(4, 200, 10, doc="number of spiral spline points")
ratio_start = FloatRange(0.5, 0.99, 0.99, doc="radius ratio of wedge start")
ratio_end = FloatRange(0.01, 0.8, 0.7, doc="radius ratio of wedge end")
_render = render_props(color=(100, 100, 150)) # dark blue
@property
def wedge_radii(self):
return (
(self.diameter / 2) * self.ratio_start, # start radius
(self.diameter / 2) * self.ratio_end # end radius
)
def make(self):
obj = cadquery.Workplane('XY') \
.circle(self.diameter / 2) \
.extrude(-self.height)
# neck slot : eliminate screw neck interference
obj = obj.cut(
cadquery.Workplane('XY', origin=(0, 0, -((self.neck_diameter + self.height) / 2))) \
.moveTo(0, 0) \
.lineTo(self.diameter / 2, 0) \
.threePointArc(
(0, -self.diameter / 2),
(-self.diameter / 2, 0),
) \
.close() \
.extrude(self.neck_diameter)
)
# head slot : form a circular wedge with remaining material
(start_r, end_r) = self.wedge_radii
angles_radius = ( # as generator
(
(i * (pi / self.spline_point_count)), # angle
start_r + ((end_r - start_r) * (i / float(self.spline_point_count))) # radius
)
for i in range(1, self.spline_point_count + 1) # avoid zero angle
)
points = [(cos(a) * r, -sin(a) * r) for (a, r) in angles_radius]
obj = obj.cut(
cadquery.Workplane('XY', origin=(0, 0, -((self.head_diameter + self.height) / 2))) \
.moveTo(start_r, 0) \
.spline(points) \
.close() \
.extrude(self.head_diameter)
)
# access port : remove a quadrant to allow screw's head through
obj = obj.cut(
cadquery.Workplane('XY', origin=(0, 0, -(self.height - self.head_diameter) / 2)) \
.rect(self.diameter / 2, self.diameter / 2, centered=False) \
.extrude(-self.height)
)
# screw drive : to apply torque to anchor for installation
if self.drive:
obj = self.drive.apply(obj) # top face is on origin XY plane
return obj
def make_simple(self):
# Just return the core cylinder
return cadquery.Workplane('XY') \
.circle(self.diameter / 2) \
.extrude(-self.height)
def make_cutter(self):
# A solid to cut away from another; makes room to install the anchor
return cadquery.Workplane('XY', origin=(0, 0, -self.height)) \
.circle(self.diameter / 2) \
.extrude(self.height + 1000) # 1m bore depth
@property
def mate_screwhead(self):
# The location of the screwhead in it's theoretical tightened mid-point
# (well, not really, but this is just a demo)
(start_r, end_r) = self.wedge_radii
return Mate(self, CoordSystem(
origin=(0, -((start_r + end_r) / 2), -self.height / 2),
xDir=(1, 0, 0),
normal=(0, 1, 0)
))
@property
def mate_center(self):
# center of object, along screw's rotation axis
return Mate(self, CoordSystem(origin=(0, 0, -self.height / 2)))
@property
def mate_base(self):
# base of object (for 3d printing placement, maybe)
return Mate(self, CoordSystem(origin=(0, 0, -self.height)))
What we’ve made isn’t perfect, but it will do for this tutorial. Besides, that’s more than enough code:
anchor = Anchor()
display(anchor)
Wood Panel¶
We’ll also need to create some wooden panels that we intend to join using the fastener.
It’s essentially just a box shape, but we’ll add some mate points to allow easy alignment.
class WoodPanel(cqparts.Part):
thickness = PositiveFloat(15, doc="thickness of panel")
width = PositiveFloat(100, doc="panel width")
length = PositiveFloat(100, doc="panel length")
def make(self):
return cadquery.Workplane('XY') \
.box(self.length, self.width, self.thickness)
@property
def mate_end(self):
# center of +x face
return Mate(self, CoordSystem(
origin=(self.length / 2, 0, 0),
xDir=(0, 0, -1),
normal=(-1, 0, 0),
))
def get_mate_edge(self, thickness):
return Mate(self, CoordSystem(
origin=((self.length / 2) - (thickness / 2), 0, self.thickness / 2)
))
panel = WoodPanel()
display(panel)
Now that we have all the parts we need, the next section will work on combining these into an assembly.