Programmatic generation of an openUC2 cube insert and component cutouts using CadQuery.
New:
uc2v4/— exact parametric reconstructions of the molded V4 inserts. Built from the Inventor master models (COM extraction, seeextracted/README.md) and verified against the released STEP geometry:Square-insert family (
build_uc2v4.py):
uc2v4.build_master_insert()→ PRT - 2123 - MASLCK - V04 - B (4 mm master-insert plate: springs, corner ribs, 82° cone opening, 8×45° nose grooves, self-mating screw pattern)uc2v4.build_lens_insert()→ PRT - 2027 - INSLEND43F-50 - V04 (17 mm lens insert: parametriclens_diam, pocket/seat/aperture, 2-turn pitch-1.7 clamping thread for the pre-screw ring)Round-holder family (
build_mirror_holder.py):
uc2v4.round_holder.build_base_holder()→ the reusable blank: the conic disk whose 8 noses are the exact positives of the master insert's grooves, so it seats on 45° indexinguc2v4.round_holder.build_mirror_holder()→ PRT - 2111 - MASINSMIRHOLUPP - C (obround beam aperture + sandwich screw counterbores)uv run --with cadquery --with trimesh --with rtree --with scipy python build_uc2v4.pywrites STEP/STL into
generated/and prints the mesh-deviation report againstextracted/*.step. Design write-up:DOCS-insert-v4-design.md. Both families are available to optikit-core as standalone T3 generators (openuc2.tpl.square_insert_v4,openuc2.tpl.round_holder_v4).
Automatic lens cartridges —
uc2v4/lens_cartridge.py. Give it a lens and a target pose measured from the cube centre; it snaps to the cube's notch grid (7 notches, 5.0 mm pitch, measured fromPRT - 1003 - CUBHLF111), hands the residual offset and tilt to the printed parts, and writes the front and back round inserts that clamp the lens and drop into the molded master inserts:uv run --with cadquery python uc2v4/lens_cartridge.py --diameter 25.4 --thickness 3.5 --r1 51.5 --r2 -51.5 -x 3.0 -y -2.0 -z 6.1 --extension-front 1.5 --extension-back 1.5Each run also writes a
*_layout.pngschematic of the cube, the notch grid, the molded/printed sandwich and the lens, and engraves an identifying label on each outward face.
check_lens_cartridge.pyverifies a generated pair (one solid per half, no overlap, zero lens-to-holder collision, snug pocket, pose reproduced, envelope respected, and the cone rings facing the joint — the halves are not symmetric and a mirrored build passes every other check).
Beamsplitter / fluorescence-filter cubes —
uc2v4/beamsplitter_insert.py. A printable two-part clamshell (modelled on PRT-2074 / PRT-2075) that carries an excitation filter, an emission filter and a 45° dichroic through one cube. Each optic is independently a round disc or a rectangular plate, with its own thickness; the excitation reflects off the dichroic to the sample port, the emission passes straight through:uv run --with cadquery python uc2v4/beamsplitter_insert.py # the reference set uc2cad beamsplitter --exc-diam 25.4 --exc-thick 4 \ --emi-diam 25.4 --emi-thick 4 \ --dic-w 25 --dic-thick 1 --beam 18 # or via the CLIIt writes the lower and upper halves (STEP + STL) plus the plan JSON.
check_inserts.pyverifies each build (one solid per half, no overlap, every optic drops into its seat and lifts out of the split plane, all beam legs bored, pins registered). Design write-up:DOCS-beamsplitter-insert.md.
Optical-module plates —
uc2v4/opm_plates.py. The two 5 mm aluminium plates that sandwich an OPM, for any layout: a core rectangle such as3x8or3x3plus extra puzzle units on any side (3x8+1x1@-1,0), or a layout drawn as ASCII art. Built from the masterMAS - 1003 - Base plates Al - V04and the released pairs (PRT-1051/1052 FLIM 488, 1053/1054, 1026/1027, 1047/1048): 50.0 × 50.1 mm cells with four M3 holes and a Ø38 pocket each, R1.5/R10.2 corners, the outward-face chamfer, tie rods at the core's corners (every ≤ 3 cells along long edges — the released rule), counterbores on the top, sleeve-nut U-slot recesses underneath, optional M37×0.5 ports:uv run --with cadquery --with matplotlib python build_opm_plates.py # edit the layouts in there uc2cad plates --layout 3x8+1x1@-1,0 --port 1,7 # or via the CLIEach module writes the top and base plate (STEP + STL), the assembled stack, a plan JSON (cells, tie rods, ports, hardware such as
8 x ISO 4762 M3x115) and a layout diagram.check_opm_plates.pyreproduces the tie rods of all five released plate families and matches PRT-1052 feature for feature (all 149 cylindrical features, same 203 faces, volume −0.012 %). Design write-up:DOCS-opm-plates.md.
dvOPM camera, launch and sample parts —
uc2v4/opm_camera_adapter.py,uc2v4/opm_launch_holder.py,uc2v4/opm_sample_holder.py. The printed parts of the direct-view OPM on a 5x4 plate. Its detection stays in openUC2 cubes: the fold mirror and the reversed telecentric lens use the CAD-new modules ASS-2020 and ASS-3011 turned inside their cubes, and the only new detection part is the camera adapter — a double base-holder disc captured between a MASLCK and a MASINS like any printed round insert, with a bracket, a spine and a 4 mm plate that holds the Alvium bare board at the sensor tilt (50 deg) in the cube; the sensor looks through a window, four M2 hold the board from behind. The launch holder carries the RMS-threaded 4x objective, the LJ1878L1 cylinder lens (side slot + sliding key) and the f = 8 fibre collimator (side-slid fork clip); the arc-rail bracket's two concentric slots pivot it about the sheet's entry point, and the bracket hangs from the launch stage's slide when the slide stands above the holder. The sample arm is one flat slab on the sample stage's slide with the dish ring. Every dimension comes from the layout model inopenuc2-opmsimulator/opm_layout.py(opm_layout.json):uc2cad opm-camera ../openuc2-opmsimulator/opm_layout.json # camera adapter -> generated/opm_detection uc2cad opm-launch ../openuc2-opmsimulator/opm_layout.json # holder, key, clip, bracket -> generated/opm_launch uc2cad opm-sample ../openuc2-opmsimulator/opm_layout.json # sample arm + dish -> generated/opm_launch uv run --with cadquery python check_opm_launch_holder.py ../openuc2-opmsimulator/opm_layout.jsonThe camera cube as built (adapter, inserts, board) is checked in openuc2-opmsimulator (
check_opm_detection_cubes.py);check_opm_launch_holder.pyverifies the thread, the seats and retainers, the clamp screws and that every medium's screw position lies in the bracket's slots. The plan JSON'sparamsblock also drivesopenuc2-opmsimulator/build_opm_holder_ipt.py, which rebuilds the launch holder as a native parametric Inventor part (35 user parameters, coil-cut RMS thread);check_opm_holder_native.py <its .stp> generated/opm_launchsamples that part against the CadQuery reference. The beam-path solids (generated/opm_launch/opm_beam_*.step) come fromopenuc2-opmsimulator/opm_beam_solids.py.uc2v4/opm_detection_holder.pyandcheck_opm_detection_holder.pyare the superseded one-piece detection holder (2026-10-02). Pipeline write-up:openuc2-opmsimulator/OPM_CAD_README.md.
Holders for turning an optical design into cubes (2026-10-03). The geometry behind optikit-core's generators (
generators/*_v4.pythere), each with aplan_*that checks without building and acheck_*.pyhere that verifies the parts against what they hold:
module holds check round_clamp.pya cylinder body up to Ø34 mm (laser module, fibre collimator, tube) between two master inserts, one set screw check_round_clamp.pykinematic_adapter.pyany round or rectangular optic in the ½-inch kinematic mount ZJB-0.5-3, on a plate or a wedge check_kinematic_adapter.pybolt_cradle.pya device by the holes in one face: an insert across the beam, or a pedestal on a plate check_bolt_cradle.pyfocus_cartridge.pya lens in a barrel that slides ±1 mm in a round clamp, set-screw lock check_focus_cartridge.pyoff_grid_plate.pya plate in the puzzle layer over a list of cells: kinematic pedestal, saddle, cradle, lens wall; docking poses check_off_grid_plate.pycube_pocket.pya beamsplitter cube at the centre of a cell, in the fluorescence clamshell check_cube_pocket.pyslide_slot.pya microscope slide across the beam, stop, coverslip pocket check_slide_slot.pysample_vessel.pya vial or a cuvette upright across the beam, windows, light-tight cap check_sample_vessel.pycell_cover.pya light-tight cover over a list of cells with an interlock switch housing check_cell_cover.py
lens_cartridge.snap_jointplaces every master-insert pair, and its joint plane stays within ±13 mm of the cube centre (CubeInterface.joint_limit_mm; further out the outer insert sits on the cube's end frames). optikit-core pins v0.6.0 and needs a new tag for these.
FRAME objective supports —
uc2v4/frame_objective_support.py(2026-10-09). The printed part between the FRAME's kinematic objective sled (FR110-0401) and an objective, as theFRAME - 0402 / 0408 - Support objectiveparts are built (measured on them): a 5.2 mm flange with two M2.5 lugs, a Ø25.4 boss up to the objective's shoulder, the objective's thread printed in the top of the bore. Thread (RMS, M25x0.75, M26x0.706, M27x0.75, C-mount, M<Ø>x), height and label are parameters;parfocal_heightsgives one height per slot so all objectives focus in one plane (61.4 mm above the sled for two PF-45 objectives, the CAD's 16.4 mm supports). RMS fits as in the CAD; M25x0.75 and C-mount get screw pockets in a Ø29+ boss (0.6-0.8 mm wall to the pockets); M26 and wider are refused — their bore reaches the sled's M2.5 screws at r 16 mm, so they need a sled with the screws further out.check_frame_objective_support.py [--cad <FRAME-0402.stp> 16.4]checks the rules and samples a CAD part against the model. optikit-core'sgenerators/frame_objective_support.pywraps it for the FRAME wizard (needs this release, v0.8.0).
Browser wizard —
uc2cad wizard. No command line needed: run it and a page opens where you enter the numbers for a lens holder or a beamsplitter cube, or click an OPM plate layout together on a grid, and download a ZIP of the files. It uses only the Python standard library (plus CadQuery), so it runs anywhere the generators do:uv run --with cadquery python -m uc2v4.wizard # opens http://127.0.0.1:8137/
The scripts below predate the extraction and approximate the outline from drawings — still useful as simple starting points, but
uc2v4/is the measured reference.
Python-generated generic insert that can e.g. host a lens or something
Goal:
- Create a reusable insert “blank” (outer geometry + optional tabs/wings + threaded holes).
- Create separate component STEP cutters (lens pockets, motor clearances, etc.).
- Import any STEP cutter, apply an affine transform, and subtract it from the insert to produce a printable/custom holder.
Coordinate system:
- Optical axis = Z-axis
- Optical axis passes through (0, 0) in the XY-plane
- All shapes are built around the origin by default
Creates a negative volume (“cutter”) as a STEP file.
Typical use:
- Generate a lens pocket as a cylinder + optional seat + optional set-screw holes.
- Export as
component_cut.step(and optionallycomponent_cut.stl).
You can create multiple cutters, e.g.:
lens_25mm_cut.stepmotor_clearance_cut.steplaser_mount_cut.step
These STEP cutters are then consumed by the insert builder.
Creates the insert body and subtracts one or more imported STEP cutters.
What it generates:
- Outer insert outline (octagon-like profile from the technical drawing)
- Optional side tabs (or “wings” depending on the version you use)
- Optional center bore (or you do center bore via cutter STEP)
- Optional threaded/tapping holes
- Subtraction of imported cutters after applying affine transforms
- Exports
uc2_insert.stepanduc2_insert.stl
Create an environment and install CadQuery:
pip install cadqueryIf your Python environment is already set up (e.g. mambaforge), install into that environment.
python uc2_component_cut_step.pyThis writes:
component_cut.stepcomponent_cut.stl(optional)
Edit uc2_insert_builder.py and add the cutter to CUTTERS, then run:
python uc2_insert_builder.pyThis writes:
uc2_insert.stepuc2_insert.stl
A cutter is any solid STEP you want to subtract from the insert.
Typical workflow:
- Generate a cutter STEP in a dedicated script (preferred, reproducible)
- Add it to
CUTTERSlist inuc2_insert_builder.py - Assign an affine transform for positioning/orientation
- Boolean subtract (
insert.cut(cutter))
This supports:
- Lens holders (coaxial to optical axis)
- Motor pockets (offset + rotated)
- LED/laser holders
- Cable channels / clearance volumes
- Any imported CAD STEP solid
Each cutter can be positioned and rotated with:
- Translation:
tx, ty, tz(mm) - Rotation:
rx, ry, rz(degrees, applied about origin, in order X → Y → Z)
Example: shift 2 mm in X, rotate 15° around optical axis:
CUTTERS = [
("component_cut.step", Affine(tx=2.0, ty=0.0, tz=0.0, rx=0.0, ry=0.0, rz=15.0)),
]Tip:
- If you want a lens centered on the optical axis, keep
(tx, ty) = (0, 0).
In uc2_insert_builder.py:
OUTER_HALF,SHOULDER_HALF: insert outlineINSERT_THICKNESS: extrusion thicknessADD_SIDE_TABS, tab dimensionsADD_THREAD_HOLESand hole geometryCUTTERSlist and each cutter transform
In uc2_component_cut_step.py:
- Lens diameter + clearance
- Seat diameter + depth
- Set screw count, diameter, radius
This approach is meant to support openUC2’s open insert ecosystem:
- Generate inserts reproducibly
- Share scripts + parameters
- Allow others to remix and extend
If you create a useful cutter for a common component (lens size, motor, LED), publish it with:
- source script
- generated STEP
- a short usage snippet (recommended transform and parameters)
