Files
Zhu Tianyi f32fbf60bd fix(3mf): make inspect→resize work for non-default axes and concentric holes (#370)
* fix(3mf): make inspect→resize work for non-default axes and concentric holes

`inspect -a 2` could find a cylindrical hole, but `resize` then failed with
"Unknown hole_ids: []"; and even when a hole was found its diameter was wrong
and resize moved 0 vertices. Three root causes:

1. resize_holes() re-ran hole detection with hard-coded defaults and the
   resize CLI exposed no detection options, so hole_ids produced by a
   non-default `inspect` (e.g. --axis 2, --min-confidence) never lined up.
   Thread InspectParams through resize_holes() and mirror inspect's
   --planes/--min-diameter/--min-confidence/--axis options on `resize`.

2. _group_circles() clustered by centre only, so a hole's inner circle and
   the body's outer contour (both centred on the axis) merged and their radii
   averaged into a meaningless diameter. Cluster on (centre, radius).

3. A hole's axial extent came from the inset cross-section planes, so a
   through hole's rim vertices fell outside the wall-vertex band and resize
   moved nothing. Measure the extent from the actual wall vertices.

Add regression tests for concentric grouping, true inner diameter, through-hole
axial extent, and axis-param parity in resize_holes. All 122 tests pass.

* fix(3mf): bound wall-vertex extent to the detected hole span

Address review: _wall_axial_extent scanned every vertex by radius, so a
coaxial same-radius feature elsewhere along the axis could stretch
axis_min/axis_max past the inspected hole and make resize move unrelated
vertices. Restrict the wall-vertex search to this group's detected span,
extended by one sampling inset (shared backend.PLANE_INSET_FRACTION) and
clamped to the mesh bounds, so through-hole rims are still recovered.
Adds a regression test.

* fix(3mf): reject outer contours so only interior holes are reported

Address review: splitting circle groups by radius surfaced a round body's
outer perimeter (annulus section) as its own high-confidence group, so inspect
listed the part exterior as a resizable hole. Emit a hole only when the mesh has
material radially outside the circle within its axial band -- true for an
interior hole, false for the outer boundary, a boss, or a bare cylinder. The
washer test now asserts the Ø40 outer wall is rejected; adds a solid-cylinder
rejection test.

* fix(3mf): classify holes by enclosure at both axial ends

Address review: the "material outside the radius" check kept a solid boss that
stands on a wider base, because the base rim vertices sit in the boss's axial
band and are farther out than the boss radius. Require body material radially
beyond the circle near BOTH ends of the extent instead: an interior hole is
enclosed at both faces (or, for a blind hole, by its floor), while a boss is
enclosed only at its base. Outer boundaries and bare cylinders remain rejected.
Adds a boss-on-plate rejection test.

* fix(3mf): classify holes by wall-normal orientation (keep blind holes)

Address review: the both-ends enclosure check discarded blind pockets, whose
closed-floor end has no vertices beyond the hole radius. Replace it with a
surface-normal test: a hole's cylindrical wall faces the void so its normals
point toward the axis, while an outer boundary, a bare cylinder, or a solid
boss faces outward. This keeps through AND blind holes and still rejects those
exterior contours, using the mesh's own face normals (no new dependency). If
winding is inconsistent or the wall can't be sampled, keep the candidate rather
than drop a real hole. Adds a blind-hole retention test.
2026-07-09 20:04:41 +08:00
..

cli-anything-3mf

3MF Mesh Geometry Editor — Detect and resize cylindrical holes, repair meshes, compare 3D printing files.

Part of the CLI-Anything ecosystem.

Installation

pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=3MF/agent-harness

Or for development:

cd 3MF/agent-harness
pip install -e ".[dev]"

Quick Start

# Show mesh info
cli-anything-3mf info model.3mf

# Detect cylindrical holes
cli-anything-3mf inspect model.3mf

# Resize holes 0,1,2,3 to 4.2mm diameter
cli-anything-3mf resize model.3mf --hole 0 --hole 1 --hole 2 --hole 3 --diameter 4.2 -o output.3mf

# Repair mesh issues
cli-anything-3mf repair model.3mf -o repaired.3mf

# Compare two files
cli-anything-3mf compare original.3mf modified.3mf

# Interactive REPL
cli-anything-3mf

Commands

Command Description
info <file> Show mesh statistics (vertices, faces, bounds, watertight, volume)
inspect <file> Detect and list all cylindrical holes with diameter and position
resize <file> Resize specified holes to a target diameter
repair <file> Fix degenerate faces, duplicate vertices, and normals
compare <f1> <f2> Side-by-side comparison of two 3MF files

All commands support --json for machine-readable output.

How It Works

Hole Detection

The tool takes cross-sections at multiple planes perpendicular to the hole axis, identifies circular features via least-squares circle fitting, and groups them across planes to detect consistent cylindrical holes.

Hole Resizing

Wall vertices are identified by their radial distance from the hole axis. Vertices are then scaled radially to achieve the target diameter while preserving mesh topology and angular position.

Slicer Compatibility

All non-mesh content (slicer settings, thumbnails, plate configurations) is preserved during file repack. Output files can be reopened in BambuStudio or PrusaSlicer without configuration loss.

Triangle-level mesh attributes are preserved for triangles that survive an edit, including pid, p1, p2, p3, and unknown vendor attributes. Component-only objects and component/build transform attributes are kept as original XML, but this CLI does not currently apply or edit component-instance transforms.

Architecture

cli_anything/threemf/
├── threemf_cli.py          # Click CLI + REPL
├── core/
│   ├── parser.py           # 3MF ZIP/XML parsing
│   ├── inspector.py        # Cross-section hole detection
│   ├── modifier.py         # Hole resizing
│   └── repair.py           # Mesh repair
├── utils/
│   ├── threemf_backend.py  # Geometry utilities (trimesh/numpy)
│   └── repl_skin.py        # Unified REPL styling
├── skills/
│   └── SKILL.md            # Agent-discoverable skill
└── tests/
    ├── test_core.py        # Unit tests (74+)
    └── test_full_e2e.py    # E2E tests (30+)

Dependencies

  • Python 3.10+
  • numpy, scipy, trimesh
  • click, prompt-toolkit

License

MIT — See CLI-Anything LICENSE