Skip to content

quick fiduciary 3d printable generator#3166

Open
leshy wants to merge 11 commits into
mainfrom
ivan/feat/printable_tags
Open

quick fiduciary 3d printable generator#3166
leshy wants to merge 11 commits into
mainfrom
ivan/feat/printable_tags

Conversation

@leshy

@leshy leshy commented Jul 24, 2026

Copy link
Copy Markdown
Member

quick fiduciary 3d printable generator, I don't have a paper printer :D

dimos apriltag --3d --ids 0 --legs 250 --size-mm 75

2026-07-24_14-30

`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-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds a CLI-driven 3D-printable fiducial generator and inverted-marker detection support.

  • Generates coordinated STL and colored 3MF tag parts, optional mounting legs, and a companion PDF.
  • Adds request descriptions, output summaries, geometry and 3MF tests, and required mesh dependencies.
  • Propagates an inverted-marker option through mapping and fiducial detection.

Confidence Score: 4/5

The PR should not merge until invalid 3D configurations are rejected before any output files are written.

Geometry validation currently occurs after PDF generation, so a failed invocation leaves a partial output set despite reporting a parameter error.

dimos/utils/cli/apriltag.py

Important Files Changed

Filename Overview
dimos/utils/cli/apriltag.py Introduces TagRequest orchestration and shared tag helpers, but invalid 3D geometry can be rejected only after the PDF has been written.
dimos/utils/cli/apriltag3d.py Implements mesh, mounting-leg, STL, and 3MF generation with geometry validation and output tests.
dimos/robot/cli/dimos.py Expands the apriltag CLI with 3D-printing options and converts request ValueErrors into Typer parameter errors.
dimos/perception/fiducial/marker_transformer.py Preserves inverted-marker detection when constructing and refreshing the ArUco detector.
pyproject.toml Adds trimesh and manifold3d to the apriltag extra for mesh generation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  CLI["dimos apriltag"] --> Request["TagRequest"]
  Request --> PDF["PDF generator"]
  Request --> Mesh["3D mesh generator"]
  Mesh --> STL["STL parts"]
  Mesh --> MF["Colored 3MF"]
  Map["dimos map --marker-inverted"] --> Detector["ArUco detector"]
  Detector --> Detections["Fiducial detections"]
Loading

Reviews (6): Last reviewed commit: "test(apriltag): stop paying for the back..." | Re-trigger Greptile

Comment thread dimos/robot/cli/dimos.py Outdated
Comment thread dimos/utils/cli/apriltag3d.py Outdated
@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
3030 1 3029 177
View the top 1 failed test(s) by shortest run time
dimos.codebase_checks.test_no_all::test_no_all
Stack Traces | 6.42s run time
def test_no_all():
        """Fail if any file defines `__all__`."""
        dimos_dir = DIMOS_PROJECT_ROOT / "dimos"
        hits = find_all_definitions()
        if hits:
            listing = "\n".join(f"  - {p.relative_to(dimos_dir)}:{lineno}" for p, lineno in hits)
>           raise AssertionError(
                f"Found __all__ definition(s) in dimos/:\n{listing}\n\n"
                "__all__ is not allowed. We don't use `from x import *`, so __all__ "
                "lists serve no purpose and are tedious to maintain. Remove them. For "
                "an import that exists purely to be re-exported, use `# noqa: F401`."
            )
E           AssertionError: Found __all__ definition(s) in dimos/:
E             - utils/cli/apriltag3d.py:756
E           
E           __all__ is not allowed. We don't use `from x import *`, so __all__ lists serve no purpose and are tedious to maintain. Remove them. For an import that exists purely to be re-exported, use `# noqa: F401`.

dimos_dir  = PosixPath('.../dimos/dimos/dimos')
hits       = [(PosixPath('.../dimos/dimos/dimos/utils/cli/apriltag3d.py'), 756)]
listing    = '  - utils/cli/apriltag3d.py:756'

dimos/codebase_checks/test_no_all.py:51: AssertionError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

leshy added 2 commits July 24, 2026 14:50
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.
Comment thread dimos/utils/cli/apriltag.py
leshy added 7 commits July 24, 2026 15:03
…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.
Comment thread dimos/utils/cli/apriltag.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant