Skip to main content

JointKE

Joint Kinetic Engine — a validation layer for build123d assemblies.

Part files label their mating features (threads, bond pads, bores, dowel holes). An assembly-jke.py file says how those features go together. JointKE solves the placements, then runs 34 rules covering thread compatibility, engagement, torque, adhesive bond lines, fits, galvanic pairs, interference, tool access and assembly order — and tells you, with a stable JKE-… code, what will not build.

$ jke validate examples/bad-assembly-jke.py

  base~screw_l
    x JKE-S008  cover.hole_l is 4 mm across but the M5x0.8-6g fastener is 5 mm; it will not pass through
        -> ISO 273 normal fit is 5.5 mm
    x JKE-T003  M5x0.5-6H does not accept M5x0.8-6g: pitch differs: 0.5 mm vs 0.8 mm
    x JKE-T013  the fastener reaches 15 mm past the stack but the blind hole is only threaded 4 mm deep;
                it bottoms out 11 mm before the head seats, so the joint never sees preload
    x JKE-A001  'shroud' sits in the 8.5 mm x 28 mm column the folded hex key needs above screw_m5x20.thread
    x JKE-M001  MAGNESIUM-AZ31 against STEEL-ZINC-PLATED is a 0.50 V galvanic couple ...

Install

pip install -e ".[dev]"      # build123d + pytest
pip install -e .             # engine only; geometric rules report themselves as skipped

Part files

A part file stays an ordinary build123d script. Add one import and declare the features that mate:

from build123d import *
import jke

with BuildPart() as bracket:
    Box(40, 20, 10)
    with Locations((0, 0, 5)):
        Hole(radius=2.1, depth=8)          # M5 tap drill

p = jke.part("bracket", bracket, material="AL-6061-T6")
p.thread("mount_a", "M5x0.8-6H", face=p.bore(diameter=4.2))   # depth, axis, blind-ness measured
p.bond_face("pad", face=p.face(normal=(0, 0, -1)), adhesive="3M-DP420", gap=0.2)

Or mark the face inline and let jke.part scan for it:

jke.mark(bracket.faces().sort_by(Axis.Z)[-1], "bond", "lid_pad", adhesive="DP420", gap=0.2)

Every declaration accepts either a face= to measure from or explicit numbers (at=, depth=, diameter=…). Where both are given, the declaration wins and JKE-D00x flags any disagreement — so a model can be checked before the geometry is finished.

declaration what it labels
thread(name, spec, …) tapped hole or external thread — "M5x0.8-6H", "1/4-20 UNC-2B", "G1/4", "1/4-18 NPT"
clearance(name, for_thread="M5", thickness=…) a through hole a fastener passes through (sized from ISO 273)
bond_face(name, …) / seat(name, …) an adhesive pad / a face that seats on another
bore_fit / shaft_fit / dowel_hole / dowel_pin cylinders entering a fit — iso_fit="H7" fills in ISO 286 deviations

Assembly files

import jke
from parts import plate, cover, hardware

screw = hardware.socket_screw("m4x12", "M4x0.7-6g", length=12)

asm = jke.Assembly("sensor_head", environment=jke.Environment("harsh", temp_max=85))
asm.add(plate.plate, "plate")                     # first part in is the datum
asm.add(cover.lid, "cover")
asm.add(screw, "screw_a")

asm.bolt("screw_a.thread", into="plate.mount_a", through=["cover.hole_a"],
         engagement=9.0, torque=2.2, threadlocker="LOCTITE-243")
asm.bond("cover.pad", "pcb.underside", adhesive="SIL-RTV-732", gap=0.6)
asm.press_fit("plate.seat", "bushing.outer")
asm.dowel("plate.dowel_l", "pin_l.lower")

asm.validate().raise_for_errors()

The solver

Nothing has to be pinned. Every unpinned part is a rigid body with six degrees of freedom; every join contributes geometric constraints (coaxial for threads, fits, dowels and each clearance hole in a fastener stack; planar for seats; footprint-coincident for bonds and welds). A breadth-first propagation gives a starting pose, then a Levenberg–Marquardt fit satisfies all the constraints at once. What comes out:

  • Over-constrained — constraints that will not go to zero are reported with their residual (JKE-G001–G004, G009). Two plates whose hole patterns don't match produce exactly this: one bolt lets them slide into line, two or more can't all be satisfied, and the report names each hole and by how much it misses (net of the clearance the hole forgives).
  • Under-constrained — the Jacobian's null space lists motions nothing prevents: a plate on a flat face with one bolt "can rotate about z" (JKE-G011). A screw spinning about its own axis is filtered out.
  • Pinned parts — asm.add(..., at=(x, y, z)) removes a body from the variables; joins touching it become checks on the declared placement.
$ jke validate examples/pattern-mismatch-jke.py      # 1/4" vs 1/8" corner insets
  one_corner    0 errors          (the plates simply slide 1/8")
  two_corners   x JKE-G009  upper.ne is 4.365 mm off the axis of lower.ne (a 6.6 mm hole
                            forgives 0.125 mm); the hole patterns on 'upper' and 'lower' do not match

Pure Python; no numpy.

Running

jke validate assembly-jke.py                  # exit 1 if any error
jke validate assembly-jke.py --strict --format json -o report.json
jke validate assembly-jke.py --select JKE-T --ignore JKE-S002
jke validate assembly-jke.py --severity JKE-M001=info --environment controlled
jke inspect parts/base_plate.py               # list ports with frames
jke thread "1/4-20 UNC-2B"                    # dump the numbers behind a designation
jke rules                                     # the catalogue
jke doctor

python assembly-jke.py works too — asm.validate() returns a Report with .errors, .warnings, .to_text(), .to_markdown(), .to_json().

What gets checked

See docs/rules.md for the full table. In outline:

family covers
T threads standard / diameter / pitch / hand / starts / taper match, tolerance-class genders and allowance, engagement vs material (1×D steel … 3×D printed plastic), bottoming in blind holes, protrusion through tapped holes, stripping length, torque vs proof load and vs thread shear, threadlocker suitability, tapping plastics, stainless galling, sealing on parallel threads
S structure fastener passes through its clearance stack, head bearing / counterbore, grip length vs shank, ports used once, unused labels, disconnected instances, unplaceable parts, wrong port kinds
B bonding adhesive named, bond-line inside the qualified window (incl. fixed-thickness tapes), substrate qualification and surface energy, service temperature, shear stress vs allowable, peel/cleavage loading, CTE-mismatch strain
F fits a 10 mm shaft does not go in a 4 mm bore (and a 4 mm shaft rattles in a 10 mm one), ISO 286 classes vs intent (press / slip / transition), Lamé hoop stress in the hub, loss of interference at tolerance extremes or temperature, press force estimate, polymer relaxation, over-doweling
G geometry axial / radial / angular residuals on every join and every clearance hole in a stack (hole-pattern mismatch), under-constrained motions, faces that don't oppose, port normals pointing into their own part, solid interference net of what each join legitimately explains
A access driver envelope and swing room above each head, counterbore admits the tool, insertion corridor, a topological assembly order exists, permanent joints in a serviceable assembly
M materials galvanic couples vs environment, service-temperature limits, thermal preload change, polymers under clamp load
D declaration tap-drill sanity (modelled vs minor diameter), declared vs measured lengths, partial cylindrical faces, through-hole mouth ambiguity, missing solids / materials

Rules never raise: anything that can't be evaluated (no kernel, missing material) becomes a skipped finding. Add your own with @jke.rules.register on a JoinRule or AssemblyRule subclass.

Layout

jke/
  part.py, assembly.py        authoring API
  ports.py, joins.py          the model the rules see
  solver.py, constraints.py,  rigid-body constraint solver (LM + null-space analysis);
  numeric.py, _math.py          dependency-free linear algebra and 3D math
  geometry.py                 the only module that imports build123d
  threads.py, fits.py,        ISO 68/965/261, ASME B1.1, ISO 286, ISO 273,
  fasteners.py, materials.py,   MIL-STD-889 anodic indices, adhesive windows
  adhesives.py
  rules/                      one file per family; each rule has a stable code
  report.py, config.py, cli.py
examples/                     a clean assembly, a deliberately broken one, and the
                                mismatched-hole-pattern plates
tests/                        115 tests; CAD-dependent ones skip without build123d

Units are millimetres, degrees, newtons, MPa. Strings like "0.25in" or "1/4 in" are accepted wherever a length is.

Metadata

Release files for jointke 0.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jointke 0.0.1
File Size Uploaded
jointke-0.0.1.tar.gz 113.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for jointke 0.0.1
File Interpreter ABI Platform
jointke-0.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 229.0 kB

Release files / jointke-0.0.1.tar.gz

Download URL jointke-0.0.1.tar.gz
Size 113.6 kB
Tags Source
SHA-256 checksum
How to use checksums
d07287dbef5006be0ba1b4b4123a495dcb8c1947dd1d29d553d7fd6fcfad2651
BLAKE2b-256 checksum
How to use checksums
edb3bbea48e1ac0c09e48def0c508e3e8895e2b9f077189028415deed37a99c1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 21, 2026.

Transparency log

Release files / jointke-0.0.1-py3-none-any.whl

Download URL jointke-0.0.1-py3-none-any.whl
Size 115.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cf7708af6d7fc12ff9c827c71ea598be2d36561b3f2b0a9823c764b2140f14c6
BLAKE2b-256 checksum
How to use checksums
10362c45c2208c28be9ea0fdeb948bb0766f70c7936a9ebf59cb0d7e2a2544f2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.0.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page