Skip to content

Parametric Hardware Library from Engineering Standards

Parametric Hardware Library from Engineering Standards hero image
Modified:
Published:

Build a complete library of ISO-standard fasteners, including hex bolts, socket cap screws, nuts, and washers, all generated from engineering tables using CadQuery and Python. Instead of downloading inaccurate CAD models from the web, you will produce 50+ STEP and STL files from authoritative ISO dimensions that you can reuse across every project. #CadQuery #ISOFasteners #ParametricCAD

Learning Objectives

By the end of this lesson, you will be able to:

  1. Set Up CadQuery with Jupyter and OCP CAD Viewer for interactive 3D development
  2. Translate ISO standards tables into Python dictionaries for parametric design
  3. Generate helical thread geometry from ISO metric thread equations
  4. Build parametric functions for hex bolts, socket cap screws, nuts, and washers
  5. Export a batch library of 50+ standard parts as STEP and STL files
  6. Validate generated dimensions against ISO specifications

Environment Setup



Before writing any CAD code, you need CadQuery (the geometry kernel) and a way to run Python scripts. You can use Jupyter notebooks for interactive, cell-by-cell exploration, or plain Python scripts run from the terminal. Both approaches produce the same STEP, STL, and GLB output files. CadQuery requires Python 3.9 or newer (3.10+ recommended). Check your version with python3 --version before proceeding.

  1. Create a virtual environment with Python 3.10+:

    Terminal window
    python3.10 -m venv cadquery-env
    source cadquery-env/bin/activate

    If python3.10 is not available, install it first:

    Terminal window
    # Ubuntu/Debian
    sudo apt install python3.10 python3.10-venv
    # macOS (Homebrew)
    brew install [email protected]
  2. Install CadQuery, Jupyter, and trimesh (for GLB export):

    Terminal window
    pip install --upgrade pip
    pip install cadquery ocp-vscode jupyter trimesh
  3. Verify the installation:

    Option A: Jupyter notebook (interactive, cell-by-cell):

    Terminal window
    jupyter notebook

    Create a new notebook and run:

    import cadquery as cq
    import trimesh
    box = cq.Workplane("XY").box(10, 10, 10)
    cq.exporters.export(box, "test_box.step")
    cq.exporters.export(box, "test_box.stl")
    # Convert STL to GLB for web viewers
    mesh = trimesh.load("test_box.stl")
    mesh.export("test_box.glb")
    print("CadQuery is working! Exported test_box.step, .stl, and .glb")

    Option B: Plain Python script (no Jupyter required): Save the same code above as test_cadquery.py and run:

    Terminal window
    python3 test_cadquery.py

    Both approaches produce identical output files.

ISO Standards Reference



Every dimension in this lesson comes from published ISO standards. The four fastener types we will generate correspond to these documents:

ISO 4014: Hex Bolts

Hexagon head bolts with shank. Defines head height, across-flats width, and thread length for each nominal size.

ISO 4762: Socket Cap Screws

Hexagon socket head cap screws. Defines head diameter, head height, and socket size for internal hex drive.

ISO 4032: Hex Nuts

Hexagon nuts, Style 1. Defines across-flats width, across-corners width, and nut height for each size.

ISO 7089: Flat Washers

Plain washers, normal series. Defines inner diameter, outer diameter, and thickness for each nominal size.

ISO Metric Thread Dimensions (ISO 261 / ISO 724)

The foundation of every fastener is the thread. ISO metric threads are defined by the nominal diameter and the pitch . All other thread dimensions derive from these two values.

SizeNominal Diameter (mm)Coarse Pitch (mm)Minor Diameter (mm)Pitch Diameter (mm)
M33.0000.5002.3872.675
M44.0000.7003.1413.545
M55.0000.8004.0194.480
M66.0001.0004.7735.350
M88.0001.2506.4667.188
M1010.0001.5008.1609.026
M1212.0001.7509.85310.863
M1414.0002.00011.54612.701
M1616.0002.00013.54614.701
M2020.0002.50016.93318.376

Key thread equations (ISO 68-1):

The basic thread profile follows these relationships:

where is the fundamental triangle height. The major diameter equals the nominal diameter . The pitch diameter is:

And the minor diameter is:

ISO 4014: Hex Bolt Dimensions

SizeThread Pitch (mm)Head Width Across Flats (mm)Head Height (mm)Default Thread Length (mm, for )
M30.505.502.012
M40.707.002.814
M50.808.003.516
M61.0010.004.018
M81.2513.005.322
M101.5016.006.426
M121.7518.007.530
M142.0021.008.834
M162.0024.0010.038
M202.5030.0012.546

ISO 4762: Socket Head Cap Screw Dimensions

SizeThread Pitch (mm)Head Diameter (mm)Head Height (mm)Socket Size (mm)
M30.505.503.02.5
M40.707.004.03.0
M50.808.505.04.0
M61.0010.006.05.0
M81.2513.008.06.0
M101.5016.0010.08.0
M121.7518.0012.010.0
M142.0021.0014.012.0
M162.0024.0016.014.0
M202.5030.0020.017.0

ISO 4032: Hex Nut Dimensions

SizeThread Pitch (mm)Width Across Flats (mm)Width Across Corners (mm)Nut Height (mm)
M30.505.506.012.4
M40.707.007.663.2
M50.808.008.794.7
M61.0010.0011.055.2
M81.2513.0014.386.8
M101.5016.0017.778.4
M121.7518.0020.0310.8
M142.0021.0023.3612.8
M162.0024.0026.7514.8
M202.5030.0033.5318.0

ISO 7089: Flat Washer Dimensions

SizeInner Diameter (mm)Outer Diameter (mm)Thickness (mm)
M33.27.00.5
M44.39.00.8
M55.310.01.0
M66.412.01.6
M88.416.01.6
M1010.520.02.0
M1213.024.02.5
M1415.028.02.5
M1617.030.03.0
M2021.037.03.0

Standards Data in Python



The first step is encoding these ISO tables as Python dictionaries. This is the parametric foundation: every function will look up dimensions from these dictionaries.

import cadquery as cq
import math
import os
# ─── ISO Metric Thread Data (ISO 261) ───
THREAD_DATA = {
"M3": {"d": 3.0, "P": 0.50},
"M4": {"d": 4.0, "P": 0.70},
"M5": {"d": 5.0, "P": 0.80},
"M6": {"d": 6.0, "P": 1.00},
"M8": {"d": 8.0, "P": 1.25},
"M10": {"d": 10.0, "P": 1.50},
"M12": {"d": 12.0, "P": 1.75},
"M14": {"d": 14.0, "P": 2.00},
"M16": {"d": 16.0, "P": 2.00},
"M20": {"d": 20.0, "P": 2.50},
}
# ─── ISO 4014: Hex Bolt Head Dimensions ───
HEX_BOLT_DATA = {
"M3": {"s": 5.5, "k": 2.0, "b": 12},
"M4": {"s": 7.0, "k": 2.8, "b": 14},
"M5": {"s": 8.0, "k": 3.5, "b": 16},
"M6": {"s": 10.0, "k": 4.0, "b": 18},
"M8": {"s": 13.0, "k": 5.3, "b": 22},
"M10": {"s": 16.0, "k": 6.4, "b": 26},
"M12": {"s": 18.0, "k": 7.5, "b": 30},
"M14": {"s": 21.0, "k": 8.8, "b": 34},
"M16": {"s": 24.0, "k": 10.0, "b": 38},
"M20": {"s": 30.0, "k": 12.5, "b": 46},
}
# ─── ISO 4762: Socket Head Cap Screw Dimensions ───
SOCKET_CAP_DATA = {
"M3": {"dk": 5.5, "k": 3.0, "s_hex": 2.5},
"M4": {"dk": 7.0, "k": 4.0, "s_hex": 3.0},
"M5": {"dk": 8.5, "k": 5.0, "s_hex": 4.0},
"M6": {"dk": 10.0, "k": 6.0, "s_hex": 5.0},
"M8": {"dk": 13.0, "k": 8.0, "s_hex": 6.0},
"M10": {"dk": 16.0, "k": 10.0, "s_hex": 8.0},
"M12": {"dk": 18.0, "k": 12.0, "s_hex": 10.0},
"M14": {"dk": 21.0, "k": 14.0, "s_hex": 12.0},
"M16": {"dk": 24.0, "k": 16.0, "s_hex": 14.0},
"M20": {"dk": 30.0, "k": 20.0, "s_hex": 17.0},
}
# ─── ISO 4032: Hex Nut Dimensions ───
HEX_NUT_DATA = {
"M3": {"s": 5.5, "e": 6.01, "m": 2.4},
"M4": {"s": 7.0, "e": 7.66, "m": 3.2},
"M5": {"s": 8.0, "e": 8.79, "m": 4.7},
"M6": {"s": 10.0, "e": 11.05, "m": 5.2},
"M8": {"s": 13.0, "e": 14.38, "m": 6.8},
"M10": {"s": 16.0, "e": 17.77, "m": 8.4},
"M12": {"s": 18.0, "e": 20.03, "m": 10.8},
"M14": {"s": 21.0, "e": 23.36, "m": 12.8},
"M16": {"s": 24.0, "e": 26.75, "m": 14.8},
"M20": {"s": 30.0, "e": 33.53, "m": 18.0},
}
# ─── ISO 7089: Flat Washer Dimensions ───
WASHER_DATA = {
"M3": {"d1": 3.2, "d2": 7.0, "h": 0.5},
"M4": {"d1": 4.3, "d2": 9.0, "h": 0.8},
"M5": {"d1": 5.3, "d2": 10.0, "h": 1.0},
"M6": {"d1": 6.4, "d2": 12.0, "h": 1.6},
"M8": {"d1": 8.4, "d2": 16.0, "h": 1.6},
"M10": {"d1": 10.5, "d2": 20.0, "h": 2.0},
"M12": {"d1": 13.0, "d2": 24.0, "h": 2.5},
"M14": {"d1": 15.0, "d2": 28.0, "h": 2.5},
"M16": {"d1": 17.0, "d2": 30.0, "h": 3.0},
"M20": {"d1": 21.0, "d2": 37.0, "h": 3.0},
}

Thread Generation



Generating realistic helical threads is the most complex part of fastener modeling. ISO 60-degree threads follow a specific cross-section profile swept along a helix.

Thread Profile Geometry

The ISO metric thread profile is a truncated triangle with a 60-degree included angle. The key cross-section parameters are:

The external thread (bolt) has a flat crest and a rounded root:

  • Crest truncation:
  • Root truncation:

The actual thread height (from root to crest of the external thread) is:

Helical Thread Function

def make_thread(diameter, pitch, length, external=True):
"""
Generate an ISO metric thread using a helical sweep.
Parameters:
diameter (float): Nominal diameter in mm (e.g., 6.0 for M6)
pitch (float): Thread pitch in mm (e.g., 1.0 for M6 coarse)
length (float): Thread length in mm
external (bool): True for bolt thread, False for nut thread
Returns:
cq.Workplane: CadQuery solid of the threaded cylinder
"""
# Fundamental triangle height (ISO 68-1)
H = (math.sqrt(3) / 2.0) * pitch
# Thread cross-section parameters
if external:
# External thread (bolt): major diameter = nominal
d_major = diameter
d_minor = diameter - 2 * (5/8) * H
# Simplified: create thread as a helical cut on a cylinder
thread_depth = (5/8) * H
else:
# Internal thread (nut): minor diameter = nominal - 2*(5/8)*H
d_major = diameter + 2 * (1/8) * H # slight clearance
d_minor = diameter - 2 * (5/8) * H
thread_depth = (5/8) * H
# Number of full turns
n_turns = length / pitch
# Build the base cylinder
if external:
base = (
cq.Workplane("XY")
.circle(d_major / 2.0)
.extrude(length)
)
else:
# For internal, we return a hollow cylinder (thread bore)
base = (
cq.Workplane("XY")
.circle(d_major / 2.0 + 1.0) # outer wall
.circle(d_minor / 2.0) # inner bore
.extrude(length)
)
# Create the helical thread profile
# Use a triangular cross-section swept along a helix
helix_wire = cq.Wire.makeHelix(
pitch=pitch,
height=length,
radius=d_major / 2.0 if external else d_minor / 2.0 + thread_depth,
)
# Thread cross-section: 60-degree ISO triangle (truncated)
crest_flat = H / 8.0
root_flat = H / 4.0
half_angle = math.radians(30)
# Define the triangular thread profile points
# Profile is in the XZ plane, positioned at the helix radius
r = d_major / 2.0 if external else d_minor / 2.0 + thread_depth
profile_pts = [
(r - thread_depth, -pitch / 4.0 + root_flat / 2.0),
(r, -crest_flat / 2.0),
(r, crest_flat / 2.0),
(r - thread_depth, pitch / 4.0 - root_flat / 2.0),
]
# Create the profile wire
thread_profile = (
cq.Workplane("XZ")
.moveTo(profile_pts[0][0], profile_pts[0][1])
.lineTo(profile_pts[1][0], profile_pts[1][1])
.lineTo(profile_pts[2][0], profile_pts[2][1])
.lineTo(profile_pts[3][0], profile_pts[3][1])
.close()
.wire()
)
# Sweep the profile along the helix
thread_solid = cq.Workplane("XY").sweep(thread_profile, helix_wire)
return base.union(thread_solid) if external else base.cut(thread_solid)

Simplified Thread (Cosmetic)

For faster generation and lighter file sizes, use a cosmetic thread that represents the thread as concentric cylinders at the major and minor diameters:

def make_cosmetic_thread(diameter, pitch, length, external=True):
"""
Generate a simplified cosmetic thread representation.
Uses concentric cylinders at major and minor diameters.
Much faster than helical sweep; sufficient for assembly visualization.
"""
H = (math.sqrt(3) / 2.0) * pitch
d_minor = diameter - 2 * (5/8) * H
if external:
# Bolt: cylinder at minor diameter with thread indicator rings
thread = (
cq.Workplane("XY")
.circle(diameter / 2.0)
.extrude(length)
)
# Add visual thread indicator: grooves at pitch intervals
n_grooves = int(length / pitch)
for i in range(n_grooves):
z = i * pitch + pitch / 2.0
groove = (
cq.Workplane("XY")
.workplane(offset=z)
.circle(diameter / 2.0)
.circle(d_minor / 2.0)
.extrude(pitch * 0.1)
)
thread = thread.cut(groove)
return thread
else:
# Nut: bore at minor diameter
return (
cq.Workplane("XY")
.circle(diameter / 2.0 + 2.0)
.circle(d_minor / 2.0)
.extrude(length)
)

Hex Bolt: make_hex_bolt()



def make_hex_bolt(size, length, thread_type="cosmetic"):
"""
Generate an ISO 4014 hex bolt.
Parameters:
size (str): Bolt size, e.g. "M6", "M10"
length (float): Total bolt length in mm (shank + thread)
thread_type (str): "cosmetic" for fast, "helical" for detailed
Returns:
cq.Workplane: Complete hex bolt solid
"""
# Look up ISO dimensions
td = THREAD_DATA[size]
hd = HEX_BOLT_DATA[size]
d = td["d"] # nominal diameter
P = td["P"] # thread pitch
s = hd["s"] # across-flats width
k = hd["k"] # head height
b = hd["b"] # thread length (ISO default)
# Calculate derived dimensions
e = s / math.cos(math.radians(30)) # across-corners width
thread_len = min(b, length) # actual thread length
shank_len = max(0, length - thread_len)
# ─── Head: hexagonal prism with chamfered edges ───
head = (
cq.Workplane("XY")
.polygon(6, e) # regular hexagon, across-corners
.extrude(k)
.edges("|Z")
.chamfer(0.5) # edge chamfer
.edges(">Z")
.chamfer(k * 0.15) # top chamfer
)
# ─── Shank: unthreaded cylindrical section ───
if shank_len > 0:
shank = (
cq.Workplane("XY")
.workplane(offset=-shank_len)
.circle(d / 2.0)
.extrude(shank_len)
)
else:
shank = cq.Workplane("XY") # empty
# ─── Threaded section ───
thread_start_z = -shank_len - thread_len
if thread_type == "helical":
thread = make_thread(d, P, thread_len, external=True)
else:
thread = make_cosmetic_thread(d, P, thread_len, external=True)
# Move thread to correct position
thread = thread.translate((0, 0, thread_start_z))
# ─── Thread tip: 45-degree chamfer on the end ───
chamfer_height = d * 0.3
tip_cone = cq.Solid.makeCone(d / 2.0, 0, chamfer_height).moved(
cq.Location(cq.Vector(0, 0, thread_start_z - chamfer_height))
)
# ─── Assembly ───
bolt = head
if shank_len > 0:
bolt = bolt.union(shank)
bolt = bolt.union(thread)
return bolt

Socket Head Cap Screw: make_socket_cap()



def make_socket_cap(size, length, thread_type="cosmetic"):
"""
Generate an ISO 4762 socket head cap screw.
Parameters:
size (str): Screw size, e.g. "M5", "M8"
length (float): Screw length in mm (below head)
thread_type (str): "cosmetic" or "helical"
Returns:
cq.Workplane: Complete socket cap screw solid
"""
td = THREAD_DATA[size]
sd = SOCKET_CAP_DATA[size]
d = td["d"]
P = td["P"]
dk = sd["dk"] # head diameter
k = sd["k"] # head height
s_hex = sd["s_hex"] # hex socket size (across flats)
# Socket depth is typically 60% of head height
socket_depth = k * 0.6
# ─── Head: cylindrical with hex socket ───
head = (
cq.Workplane("XY")
.circle(dk / 2.0)
.extrude(k)
.edges(">Z")
.chamfer(0.3)
)
# Cut the hex socket into the top of the head
hex_across_corners = s_hex / math.cos(math.radians(30))
socket_cut = (
cq.Workplane("XY")
.workplane(offset=k - socket_depth)
.polygon(6, hex_across_corners)
.extrude(socket_depth + 0.1) # slight overshoot for clean cut
)
head = head.cut(socket_cut)
# ─── Body: determine shank vs thread split ───
# ISO 4762: thread length b = 2d + 6 for l <= 125mm
b = 2 * d + 6
thread_len = min(b, length)
shank_len = max(0, length - thread_len)
# ─── Shank ───
if shank_len > 0:
shank = (
cq.Workplane("XY")
.workplane(offset=-shank_len)
.circle(d / 2.0)
.extrude(shank_len)
)
else:
shank = None
# ─── Threaded portion ───
if thread_type == "helical":
thread = make_thread(d, P, thread_len, external=True)
else:
thread = make_cosmetic_thread(d, P, thread_len, external=True)
thread = thread.translate((0, 0, -shank_len - thread_len))
# ─── Chamfered tip ───
# Standard 45-degree chamfer at the screw tip
# ─── Combine ───
screw = head
if shank is not None:
screw = screw.union(shank)
screw = screw.union(thread)
return screw

Hex Nut: make_nut()



def make_nut(size, thread_type="cosmetic"):
"""
Generate an ISO 4032 hex nut (Style 1).
Parameters:
size (str): Nut size, e.g. "M6", "M10"
thread_type (str): "cosmetic" or "helical"
Returns:
cq.Workplane: Complete hex nut solid
"""
td = THREAD_DATA[size]
nd = HEX_NUT_DATA[size]
d = td["d"]
P = td["P"]
s = nd["s"] # across-flats
m = nd["m"] # nut height
# Calculate across-corners from across-flats
e = s / math.cos(math.radians(30))
# Derived thread dimensions
H = (math.sqrt(3) / 2.0) * P
d_minor = d - 2 * (5/8) * H
# ─── Outer body: hexagonal prism ───
body = (
cq.Workplane("XY")
.polygon(6, e)
.extrude(m)
.edges("|Z")
.chamfer(0.4)
.edges(">Z or <Z")
.chamfer(m * 0.1)
)
# ─── Internal thread bore ───
if thread_type == "helical":
bore = make_thread(d, P, m, external=False)
else:
# Cosmetic: simple bore at minor diameter
bore = (
cq.Workplane("XY")
.circle(d / 2.0) # clearance at nominal diameter
.extrude(m)
)
# Cut the bore from the hex body
nut = body.cut(bore)
# ─── Chamfer the bore entries (both sides) ───
# 30-degree lead-in chamfer for thread engagement
chamfer_depth = P * 0.8
top_chamfer = (
cq.Workplane("XY")
.workplane(offset=m - chamfer_depth)
.circle(d / 2.0 + chamfer_depth)
.workplane(offset=chamfer_depth)
.circle(d / 2.0)
.loft()
)
bottom_chamfer = (
cq.Workplane("XY")
.circle(d / 2.0)
.workplane(offset=chamfer_depth)
.circle(d / 2.0 + chamfer_depth)
.loft()
)
# These lofted shapes remove material to create the chamfered entry
nut = nut.cut(top_chamfer).cut(bottom_chamfer)
return nut

Flat Washer: make_washer()



def make_washer(size):
"""
Generate an ISO 7089 flat washer (normal series).
Parameters:
size (str): Washer size, e.g. "M6", "M10"
Returns:
cq.Workplane: Complete flat washer solid
"""
wd = WASHER_DATA[size]
d1 = wd["d1"] # inner diameter
d2 = wd["d2"] # outer diameter
h = wd["h"] # thickness
# Create the washer as an annular disc
washer = (
cq.Workplane("XY")
.circle(d2 / 2.0)
.circle(d1 / 2.0)
.extrude(h)
)
# Optional: add a small edge break (0.1 mm chamfer)
# This improves printability and matches real washers
try:
washer = washer.edges().chamfer(min(0.1, h * 0.1))
except Exception:
pass # very thin washers may not support chamfer
return washer

Fastener Assembly Example



With all four functions built, you can assemble a complete bolted joint:

from ocp_vscode import show
def make_bolted_joint(size, bolt_length, grip_thickness):
"""
Assemble a complete bolted joint:
washer + bolt through a plate, washer + nut on the other side.
Parameters:
size (str): Fastener size (e.g., "M8")
bolt_length (float): Bolt length in mm
grip_thickness (float): Total plate thickness in mm
"""
td = THREAD_DATA[size]
hd = HEX_BOLT_DATA[size]
wd = WASHER_DATA[size]
nd = HEX_NUT_DATA[size]
# Generate components
bolt = make_hex_bolt(size, bolt_length)
nut = make_nut(size)
washer_top = make_washer(size)
washer_bottom = make_washer(size)
# Plate with clearance hole
clearance_dia = td["d"] + 1.0 # 1mm clearance
plate = (
cq.Workplane("XY")
.box(wd["d2"] * 3, wd["d2"] * 3, grip_thickness)
.faces(">Z")
.workplane()
.hole(clearance_dia)
)
# Position everything along the Z axis
# Bolt head sits on top washer, on top of plate
z_plate_top = grip_thickness / 2.0
z_plate_bottom = -grip_thickness / 2.0
# Top washer sits on plate surface
washer_top = washer_top.translate((0, 0, z_plate_top))
# Bolt head sits on top of washer
bolt_z = z_plate_top + wd["h"]
bolt = bolt.translate((0, 0, bolt_z))
# Bottom washer under the plate
washer_bottom = washer_bottom.translate(
(0, 0, z_plate_bottom - wd["h"])
)
# Nut under the bottom washer
nut_z = z_plate_bottom - wd["h"] - nd["m"]
nut = nut.translate((0, 0, nut_z))
return bolt, washer_top, plate, washer_bottom, nut
# Create an M8 bolted joint through a 10mm plate
components = make_bolted_joint("M8", 35, 10)
show(*components)

Batch Library Generation



The real power of parametric code-based design is automation. The following script generates your entire hardware library in one run: every size, every type, exported to both STEP (for CNC/assembly) and STL (for 3D printing).

import os
import cadquery as cq
def generate_hardware_library(output_dir="hardware_library"):
"""
Generate a complete ISO fastener library.
Creates subdirectories for each fastener type and exports
every size in both STEP and STL formats.
"""
# Create output directory structure
dirs = {
"bolts": os.path.join(output_dir, "hex_bolts"),
"screws": os.path.join(output_dir, "socket_cap_screws"),
"nuts": os.path.join(output_dir, "hex_nuts"),
"washers": os.path.join(output_dir, "flat_washers"),
}
for d in dirs.values():
os.makedirs(d, exist_ok=True)
sizes = ["M3", "M4", "M5", "M6", "M8", "M10", "M12", "M14", "M16", "M20"]
bolt_lengths = [10, 16, 20, 25, 30, 40, 50] # common lengths in mm
file_count = 0
# ─── Hex Bolts: each size × multiple lengths ───
print("Generating hex bolts...")
for size in sizes:
for length in bolt_lengths:
# Skip unreasonable combinations (e.g., M20x10)
min_len = HEX_BOLT_DATA[size]["b"] # minimum = thread length
if length < THREAD_DATA[size]["d"] * 1.5:
continue
name = f"ISO4014_{size}x{length}"
bolt = make_hex_bolt(size, length)
cq.exporters.export(bolt, os.path.join(dirs["bolts"], f"{name}.step"))
cq.exporters.export(bolt, os.path.join(dirs["bolts"], f"{name}.stl"))
file_count += 2
print(f" {name}")
# ─── Socket Cap Screws: each size × multiple lengths ───
print("Generating socket cap screws...")
for size in sizes:
for length in bolt_lengths:
if length < THREAD_DATA[size]["d"] * 1.5:
continue
name = f"ISO4762_{size}x{length}"
screw = make_socket_cap(size, length)
cq.exporters.export(screw, os.path.join(dirs["screws"], f"{name}.step"))
cq.exporters.export(screw, os.path.join(dirs["screws"], f"{name}.stl"))
file_count += 2
print(f" {name}")
# ─── Hex Nuts: one per size ───
print("Generating hex nuts...")
for size in sizes:
name = f"ISO4032_{size}"
nut = make_nut(size)
cq.exporters.export(nut, os.path.join(dirs["nuts"], f"{name}.step"))
cq.exporters.export(nut, os.path.join(dirs["nuts"], f"{name}.stl"))
file_count += 2
print(f" {name}")
# ─── Flat Washers: one per size ───
print("Generating flat washers...")
for size in sizes:
name = f"ISO7089_{size}"
washer = make_washer(size)
cq.exporters.export(washer, os.path.join(dirs["washers"], f"{name}.step"))
cq.exporters.export(washer, os.path.join(dirs["washers"], f"{name}.stl"))
file_count += 2
print(f" {name}")
print(f"\nLibrary complete: {file_count} files generated in '{output_dir}/'")
return file_count
# Run the generator
total = generate_hardware_library()

Expected Output Structure

After running the batch generator, your directory looks like this:

hardware_library/
├── hex_bolts/
│ ├── ISO4014_M3x10.step
│ ├── ISO4014_M3x10.stl
│ ├── ISO4014_M3x16.step
│ ├── ISO4014_M3x16.stl
│ ├── ...
│ └── ISO4014_M20x50.stl
├── socket_cap_screws/
│ ├── ISO4762_M3x10.step
│ ├── ...
│ └── ISO4762_M20x50.stl
├── hex_nuts/
│ ├── ISO4032_M3.step
│ ├── ISO4032_M3.stl
│ ├── ...
│ └── ISO4032_M20.stl
└── flat_washers/
├── ISO7089_M3.step
├── ISO7089_M3.stl
├── ...
└── ISO7089_M20.stl

GLB Export for Web Viewers

If you want to view your parts in a web-based 3D viewer or share them online, convert the STL files to GLB format using trimesh:

import trimesh
import os
from pathlib import Path
def convert_library_to_glb(library_dir: str = "hardware_library"):
"""Convert all STL files in the library to GLB format."""
count = 0
for stl_path in Path(library_dir).rglob("*.stl"):
mesh = trimesh.load(str(stl_path))
glb_path = stl_path.with_suffix(".glb")
mesh.export(str(glb_path))
count += 1
print(f"Converted {count} STL files to GLB")
convert_library_to_glb()

GLB files are compact, load quickly in browsers, and work with Three.js, model-viewer, and other web 3D libraries. You can view your GLB files directly at SiliconWit GLB Viewer.

Extending the Library



Add More Fastener Types

The same pattern works for countersunk screws (ISO 10642), set screws (ISO 4029), flange bolts (ISO 4162), and lock nuts (ISO 7040). Just add the standards table and write the geometry function.

Fine-Pitch Threads

The THREAD_DATA dictionary currently has coarse-pitch entries. Add fine-pitch data from ISO 261 (e.g., M8x1.0 instead of M8x1.25) and pass it to the same functions.

Material Properties

Extend the dictionaries with material grade data (property class 8.8, 10.9, 12.9) and attach metadata to exported files. Useful for FEA preloading and BOM generation.

Assembly Automation

Write a function that reads a bolt pattern (list of hole positions) and places the correct bolt + washer + nut at each location automatically. This is the foundation of automated assembly.

Adding a Countersunk Screw (Exercise)

As practice, try adding ISO 10642 (countersunk socket head cap screw) to the library. Here are the key dimensions to start with:

# ISO 10642: Countersunk socket head cap screw
COUNTERSUNK_DATA = {
"M3": {"dk": 6.72, "k": 1.86, "s_hex": 2.0},
"M4": {"dk": 8.96, "k": 2.48, "s_hex": 2.5},
"M5": {"dk": 11.20, "k": 3.10, "s_hex": 3.0},
"M6": {"dk": 13.44, "k": 3.72, "s_hex": 4.0},
"M8": {"dk": 17.92, "k": 4.96, "s_hex": 5.0},
"M10": {"dk": 22.40, "k": 6.20, "s_hex": 6.0},
}
def make_countersunk(size, length, thread_type="cosmetic"):
"""
Generate an ISO 10642 countersunk socket head cap screw.
The head is a truncated cone (90-degree included angle)
with a hex socket in the flat top.
"""
td = THREAD_DATA[size]
cd = COUNTERSUNK_DATA[size]
d = td["d"]
P = td["P"]
dk = cd["dk"] # head outer diameter
k = cd["k"] # head height (cone depth)
s_hex = cd["s_hex"] # socket size
# ─── Head: truncated cone ───
head = (
cq.Workplane("XY")
.circle(dk / 2.0) # bottom of cone (flush surface)
.workplane(offset=k)
.circle(d / 2.0) # top of cone (meets shank)
.loft()
)
# Cut hex socket
hex_e = s_hex / math.cos(math.radians(30))
socket_depth = k * 0.55
socket_cut = (
cq.Workplane("XY")
.polygon(6, hex_e)
.extrude(socket_depth)
)
head = head.cut(socket_cut)
# ─── Body (thread) ───
# For countersunk, almost the entire length is threaded
if thread_type == "helical":
thread = make_thread(d, P, length, external=True)
else:
thread = make_cosmetic_thread(d, P, length, external=True)
thread = thread.translate((0, 0, k)) # starts at top of cone
return head.union(thread)

Summary



What You Built

In this lesson, you built a complete parametric hardware library from ISO engineering standards:

  • Environment: CadQuery + Jupyter + OCP CAD Viewer for interactive code-based CAD
  • Data layer: ISO 261, 4014, 4762, 4032, and 7089 dimensions as Python dictionaries
  • Thread generation: both detailed helical sweep and fast cosmetic representations
  • Four parametric functions: make_hex_bolt(), make_socket_cap(), make_nut(), make_washer()
  • Batch export: automated generation of 50+ STEP/STL files in organized directories
  • Assembly: complete bolted joint from individual components

Every part is parametric: change a size string and the entire model regenerates from standards data. This library becomes the foundation for assemblies in all subsequent lessons.

Comments

Loading comments...


© 2021-2026 SiliconWit®. All rights reserved.