quick fiduciary 3d printable generator - #3166
Conversation
`dimos apriltag --3d` writes a directory holding, per tag, a base STL, a marker STL, a back-text STL and a 3MF with the colors baked in, alongside the existing PDF. The plate is a rounded rectangle whose side flanges carry four mounting holes; base and marker meet flush at the top face so the printed surface is flat rather than embossed. `--legs H` adds a matching pair of T-footed stands, H measured floor to tag centre, taking their hole spacing from the plate the tag actually generated. Also prints the resolved configuration before generating anything.
Greptile SummaryThe PR adds 3D-printable AprilTag generation and inverted-marker detection.
Confidence Score: 4/5The PR is not yet safe to merge because invalid 3D geometry can still leave a misleading partial output set. TagRequest does not validate 3D geometry before render writes the PDF, so later mesh-validation failures leave a completed PDF without the requested 3D artifacts. Files Needing Attention: dimos/utils/cli/apriltag.py Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
CLI["dimos apriltag options"] --> Request["TagRequest"]
Request --> PDF["Generate printable PDF"]
Request --> Mesh["Build plate, marker, text, and leg meshes"]
Mesh --> STL["Write component STLs"]
Mesh --> ThreeMF["Write colored 3MF"]
ThreeMF --> Print["Multicolor 3D print"]
Print --> Camera["Camera image"]
Camera --> Detector["Optional inverted-marker detector"]
Reviews (8): Last reviewed commit: "fix(apriltag3d): drop __all__, the codeb..." | Re-trigger Greptile |
Codecov Report❌ Patch coverage is @@ Coverage Diff @@
## main #3166 +/- ##
==========================================
+ Coverage 74.09% 74.18% +0.09%
==========================================
Files 1103 1105 +2
Lines 104077 104577 +500
Branches 9520 9575 +55
==========================================
+ Hits 77114 77585 +471
- Misses 24270 24283 +13
- Partials 2693 2709 +16
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 4 files with indirect coverage changes 🚀 New features to boost your workflow:
|
Review fixes: describing a request indexed its IDs and looked up its family before any error handling, so an empty --ids or an unknown --family produced a traceback rather than a parameter error; and colors went into the 3MF unchecked, so --marker-color red silently wrote an invalid displaycolor. TagRequest now validates on construction and write_3mf validates the color, with the whole CLI flow inside the error boundary. Catches --ids 10-5 too. --frame swaps the mounting holes for a picture frame emitted as its own part, to print in another material: a stepped moulding with a bead run and corner rosettes, and a rabbet the tag drops into. The lip is opaque and a different color, so it takes only what the margin can spare beyond the one-cell quiet zone the detector needs, and is refused outright when there is nothing spare. Back text now fills 90% of the plate rather than 80%, for fatter strokes.
…van/feat/printable_tags
…ults to 2 --frame now takes the band width in mm (15 is the usual) rather than being a flag, and the frame color is fixed rather than exposed. --margin-cells defaults to 2, which is what a frame needs anyway to keep the quiet zone clear of the lip. The frame's top rail carries a recess for hanging it on a nail by a rope. Both the rope and the nail head sit inside it, so the back stays flat against the wall — anything proud of the back holds the frame off the wall, and its mass, which sits in front of the wall plane, then swings it until the centre of mass hangs under the nail. The rope threads under a bar at each end of the recess; each bar bridges a short span so it prints without support, and the recess closes before the face so none of it shows from the front.
The frame's hanging recess now holds two free-standing posts, joined to the frame only at their base so a rope can pass right around each. A rope ties round both and loops over a nail above; a door above each post lets its end out through the top edge, leaving the rail solid between them. Nothing protrudes past the back plane and nothing is cut through to the face, so the frame sits flat on the wall and none of it shows from the front. --margin-cells now defaults to what the build needs rather than a flat 2: the detector wants one light cell around the black border and nothing more, and only a frame needs a second, since its lip laps over the plate edge and must leave that cell still showing. Flanges and legs reach outward and never touch the quiet zone. A bare plate is 62.5 mm rather than 75 mm for a 50 mm tag, 31% less plastic. build_tag_meshes and generate_3d default to one cell to match, which they previously did not.
The frame, its rope hanger and --frame come out so this PR stays about the printable tag itself. Kept on ivan/feat/printable_tags_frame. --margin-cells goes back to a plain default of one cell, which is what the detector wants around the black border; the second cell only ever existed for the frame's lip.
A multicolor 3D print with its two filaments swapped comes out as a photographic negative — dark plate, light tag border — and OpenCV skips it silently. Wire detectInvertedMarker through create_aruco_detector, detect_markers_in_image and DetectMarkers, and expose it as `dimos map --marker-inverted`. Found on a Go2 recording where all 432 frames detected nothing; with the flag 99 detections of tag 0 come out at 0.33 px median reprojection error. Off by default: it costs a little false-positive headroom in cluttered scenes, and the real fix is to print the tag the right way round.
Nobody picks these. The 3MF only needs the two solids to read as different filaments in the slicer, so pin them to module constants and delete --base-color / --marker-color / --text-color along with the hex validation and the colours row they existed to feed.
The mesh tests ran 11.4s for a print-file generator. Nearly all of it was the back-text raster: build_tag_meshes is 3.09s with it and 0.16s without, and it gets built twice per tag (pocket cut, then inlay). Nothing that checks plate geometry needs an engraving, so nothing here asks for one; text_solid keeps its own coverage via a single glyph. Also swaps trimesh's contains() — the only thing pulling rtree in — for a probe cube intersected with the mesh, which the boolean engine already present answers in one call. Verified identical on all 64 tag cells, and the suite passes with rtree blocked from import. 11.42s -> 1.03s, and pyproject/uv.lock go back to no rtree.
quick fiduciary 3d printable generator, I don't have a paper printer :D
dimos apriltag --3d --ids 0 --legs 250 --size-mm 75