lab-hardware-cad
Design custom laboratory hardware as parametric build123d models and export fabrication-ready STEP, STL, and DXF files - microfluidic chips and molds, optomechanical mounts and breadboard adapters, cuvette and microplate holders, tube racks, animal-behavior rigs, and 3D-printed instrument fixtures.
By k-dense-ai · 351 installs
npx skills add k-dense-ai/scientific-agent-skills --skill lab-hardware-cad
Source repository · Upstream listing
Lab Hardware CAD
Design physical research hardware as parametric Python source , export STEP as the
authoritative artifact, and verify the result both numerically and visually before anything
is fabricated.
The hard part of lab hardware is almost never the geometry. It is that the part must mate with
equipment whose dimensions are fixed by a published standard or a vendor drawing. A holder that
is 0.5 mm too wide does not fit the plate reader; a channel with the wrong aspect ratio collapses
during bonding; a mount whose bolt pattern is 25.4 mm instead of 25.0 mm will not reach the
optical table. This skill exists to keep those numbers correct and checked.
When to use
Use for any request to design, model, or fabricate a physical part for a lab: chip, mold, mount,
adapter, holder, rack, bracket, enclosure, jig, fixture, arena, or maze. Also use to inspect or
modify an existing STEP file.
Do not use for finite element analysis, computational fluid dynamics, molecular structure,
or scientific plotting. Those are different skills.
Setup
build123d 0.11.1 requires Python =3.10,<3.15 and pulls in the OpenCascade kernel through
cadquery ocp novtk . The wheel is large; install once per project and reuse it.
All bundled scripts take help . check.py standards runs without build123d installed.
Model files are executed, not parsed. gen.py , check.py , and snapshot.py import a
model.py and call its build() , which runs arbitrary Python in the current environment. That
is inherent to parametric CAD — the source is the design. Only run model files authored in this
session or supplied by the user from a trusted location. If a model came from the internet, a
shared drive, or an untrusted colleague, read it before running it and say that you did.
Required workflow
Follow these steps in order. Steps 5 and 6 are not optional, and step 6 is not waived by step 5
passing.
1. Route to a device family
Read the request, classify it, and load exactly one family reference. Do not load all four —
they are long, and mixing conventions between families is a common source of error.
If the part is Load
A chip, mold, channel network, flow cell, gasket, or anything with fluid ports references/microfluidics.md
A mount, post, breadboard adapter, cage system part, filter or sample holder in a beam path references/optomechanics.md
An adapter, insert, rack, or holder for plates, cuvettes, tubes, slides, or dishes references/labware adapters.md
An arena, maze, head fixation part, spout, tether, or extrusion mounted enclosure for animal work references/behavior rigs.md
If the part genuinely spans two families — a microfluidic chip that bolts to an optical table —
load the family that owns the critical interface , then read only the interface section of the
second. State in your response which family you routed to.
2. Establish the interface dimensions before any geometry
Every part has at least one mating interface. Before writing code, write down for each interface:
the source of the dimension: a published standard, a vendor drawing, or a user measurement;
the nominal value and tolerance ;
the clearance or interference you intend, and why.
Look the number up in assets/standards.json or the family reference. Never write an interface
dimension from memory. If the number is not in the standards file or the reference, ask the user
for the vendor drawing or the measurement rather than guessing. A guessed interface dimension is
the single most expensive failure mode in this skill.
A feature that must receive a standardised component is sized against that component's
maximum material condition — nominal plus its plus tolerance — and only then given clearance.
Sized from nominal instead, it fits only the smaller half of conforming parts.
The bundled standard IDs (exact strings; do not guess variants): slas microplate footprint ,
slas microplate height , slas microplate flange , slas well positions 96 ,
slas well positions 384 , slas well positions 1536 , cuvette standard 10mm ,
optical breadboard metric , optical breadboard imperial , cage system 30mm ,
sm1 lens tube thread .
If the part mates with nothing in this list, that is common and fine: declare no interfaces,
and name every interface dimension with its source (user spec, vendor drawing, measurement) as
unchecked in the report. Never declare against an unrelated standard to fill the gap — a
fabricated declaration is worse than an honest "nobody checked this".
3. Choose the process before choosing the geometry
Read references/fabrication limits.md . Process determines minimum wall, minimum feature,
achievable tolerance, and whether the part survives autoclaving or contact with your solvent.
FDM cannot hold ±0.05 mm; SLA resin is generally not safe for cell contact without post cure and
testing. Record the process and material in the model docstring.
4. Author a parametric model
Write <part model.py . The source is the authoritative artifact — never hand edit an exported
STEP file , and never regenerate from a mesh.
Requirements:
Every dimension that a user might change is a module level named constant with units in the
name: bore d mm , wall t mm , post h mm . No bare numbers in the body except 0, 1, and 2.
Expose build() Part . gen.py calls it.
Group parameters into an INTERFACE block (dimensions fixed by a standard, annotated with the
standard ID) and a DESIGN block (dimensions you are free to choose).
Derive every computed dimension inside a function , never at module level, so param
overrides actually reach it.
Declare an interfaces() function returning the dimensions the part must fit, each with its
standard ID and intent. This is what makes the interface machine checkable in step 5.
intent is "envelope" when the feature must accept any conforming part (a pocket, bore,
or slot — checked one sided at maximum material condition plus your clearance) and "match"
when this part must itself conform (symmetric band). clearance is the total intended
clearance in mm and must be non negative. Declare only dimensions that constrain this part's
mating features — a property of the mating equipment (a table's edge border, a typical plate
thickness) is not an interface of yours. If no bundled standard applies, return [] .
Declare a checks() function of go/no go gauges measured from the built solid : a clear
region for everything that must pass through or fit in (screw shafts, beam corridors, the
mating part at maximum material condition dropping into its pocket), a material region for
everything that must remain (a ridge, a ledge, a screw seat), and a bbox bound for every
size limit the user stated. Map every geometric requirement in the request to one entry;
these catch the errors that is valid , the bounding box, and declared numbers cannot see.
gen.py runs them on every generation and fails the build when one fails. Schema and worked
examples: references/build123d patterns.md .
Put the process, material, and every interface source in the module docstring.
See references/build123d patterns.md for the builder vs algebra choice, the interfaces()
contract, sketching, selectors, fillets, and threaded insert bores.
5. Generate and run the checks
gen.py also evaluates the model's checks() gauges against the solid it just built, prints
each PASS/FAIL, records them in the manifest, and exits non zero on a failure — so a part that
violates its own declared geometry never silently becomes an artifact. check.py geometry
re runs the same gauges against the exported STEP, which is the authoritative artifact.
out/ is a scratch convention, not a requirement. When the user asked for deliverables in a
specific place, generate there ( outdir . ) or copy the STEP, manifest, and DXF to it before
finishing — a deliverable that exists only inside out/ has not been delivered.
gen.py writes carrier.step (authoritative), carrier.stl (mesh preview and printing), and
carrier.manifest.json recording the source hash, resolved parameters, declared interfaces,
library versions, and measured bounding box, volume, and validity. The manifest is the provenance
record — keep it with the artifact.
check.py facts reports is valid , bounding box, volume, surface area, centre of mass, and
solid count. A part that reports is valid: false is broken geometry; fix the source before going
further.
check.py interfaces evaluates every entry the model declared against the standards database
and exits non zero on failure. Be clear about what it does and does not verify: it checks the
declared numbers — catching a transcribed dimension, the wrong standard, and
nominal instead of MMC sizing — but it never measures the built geometry, and a value computed
from the same constants it is checked against passes with zero headroom by construction. Do not
cite it as evidence the geometry is right; facts and the snapshot are the geometry checks.
An empty declaration list passes: a part that mates with nothing in the bundled database has
nothing to declare, and its interface dimensions are instead named as unchecked in the report.
Use interfaces rather than check.py fit for anything internal — a pocket, bore, or slot does
not appear in the part's outer bounding box, which is what fit measures. Reach for fit only
to check one number by hand ( value footprint length=128.81 ), or when the part's own outline
is the interface, such as a gasket cut to a plate footprint.
For assemblies, check that parts do not interfere:
6. Snapshot and actually look at it
Then read the PNG . This step is mandatory after every generation and every modification.
Deterministic checks passing is not a reason to skip it: is valid and a correct bounding box are
both fully consistent with a pocket cut on the wrong face, a boss placed outside the body, or a
fillet that ate a feature. Those errors are obvious in a picture and invisible in the numbers.
Know the render's limits too. A feature much smaller than the frame — a 0.3 mm mold ridge on a
40 mm part, a counterbore step on a plate — may not be decidable from the views at all. Do not
report seeing something the image cannot resolve; that is worse than not looking. For such
features the skill has instruments: check.py bores prints every cylindrical face (diameter,
axis, position, span, sweep) so you can reconcile the drilling against the model's intent, and
check.py probe answers a one off "is this region clear / is material present here" without
editing the model. Cite the measured numbers; report from the picture only what the picture
actually shows.
The six views are true orthographic projections, and the outlines are the model's real edges drawn
without hidden line removal . So a circle visible "through" material is a bore on the far side,
not a window — the part is not transparent. Read it that way rather than reporting a hole that
is not there.
State in your response what you saw in the snapshot, not merely that you generated one.
7. Repair through the source
If any check fails, edit the parameters or the model code, rerun gen.py , and rerun both
step 5 and step 6. Never patch the STEP.
8. Report before fabrication
Work through references/validation.md and give the user: the process and material, every
interface dimension with its source and tolerance, the clearances chosen, what the snapshot showed,
and any check that did not pass.
Flag explicitly every interface the automatic check could not cover — a vendor drawing, a user
measurement, a standard not in the bundled database. check.py interfaces reports only what the
model declared against a known standard, so silence there is not confirmation; a dimension nobody
could check has to be named as such.
Units
bu