[Bug]: freecad - part boolean produces no geometry, and export emits intermediate shapes instead of the final result
Affected Software/Harness
freecad
Version / Commit
cli-anything-freecad 1.0.0 (@ repo commit 810c18b0d1ab9b234bc996c9fd999318523a3ef0, main 2026-08-21); FreeCAD 1.1.3 (Revision 20260725)
Operating System
Windows
Python Version
3.12.10
Steps to Reproduce
Setup (on this machine FreeCAD is NOT on PATH and NOT under C:\Program Files,
so FREECAD_PATH is required; PYTHONUTF8 is also needed - see Additional Context)
$env:FREECAD_PATH = "D:\Program Files\FreeCAD 1.1\bin\freecadcmd.exe" $env:PYTHONUTF8 = "1"
---------- Defect A: part boolean cut produces no geometry ----------
cli-anything-freecad document new -o demo.FCStd cli-anything-freecad -p demo.FCStd part add box -n Base -P length=40 -P width=30 -P height=20 cli-anything-freecad -p demo.FCStd part add cylinder -n Hole -P radius=6 -P height=30 cli-anything-freecad -p demo.FCStd part boolean cut 0 1 -n Bracket cli-anything-freecad -p demo.FCStd export render bracket.stl -p stl --overwrite
Control experiment: identical solids, but NO boolean at all
cli-anything-freecad document new -o control.FCStd cli-anything-freecad -p control.FCStd part add box -n Base -P length=40 -P width=30 -P height=20 cli-anything-freecad -p control.FCStd part add cylinder -n Hole -P radius=6 -P height=30 cli-anything-freecad -p control.FCStd export render control.stl -p stl --overwrite
The two STL files turn out byte-for-byte identical:
sha256sum bracket.stl control.stl
---------- Defect A, engine-level confirmation ----------
-o writes the harness's JSON session, not a real FCStd, so export a real one:
cli-anything-freecad -p demo.FCStd export render real_demo.FCStd -p fcstd --overwrite
Then open it with the engine itself:
freecadcmd inspect_fc.py
-> Base Part::Box 24000.0 / Hole Part::Cylinder 3392.92
-> NO 'Bracket' object exists in the document at all
---------- Defect B: export emits intermediate shapes (PartDesign path) ----------
cli-anything-freecad document new -o pd.FCStd cli-anything-freecad -p pd.FCStd body new -n Body cli-anything-freecad -p pd.FCStd body additive-box 0 -l 40 -w 30 -h 20 cli-anything-freecad -p pd.FCStd body subtractive-cylinder 0 -r 6 -h 30 --position 20,15,-5 cli-anything-freecad -p pd.FCStd export render pd.stl -p stl --overwrite
The engine says the Body is correct: 21738.05 mm^3 (= 24000 - pi6^220)
cli-anything-freecad -p pd.FCStd export render real_pd.FCStd -p fcstd --overwrite
Expected Behavior
Defect A: part boolean cut 0 1 should create a cut solid, and exporting should produce a mesh of that cut solid (box minus cylinder). For a 40x30x20 box with an r=6 cylinder through it, the expected volume is 24000 - pi6^220 = 21738.05 mm^3.
Defect B: the exported STL/STEP should contain the project's final result only, not the intermediate PartDesign feature solids as well.
Actual Behavior
Defect A - the boolean result is never created.
Every step reports success (Boolean cut: Bracket; part list shows a part with type "cut"; export returns method: freecad-headless and a plausible file size), but the exported STL is byte-for-byte identical to a model where no boolean was performed at all. Opening the document with FreeCAD itself shows only two objects (Base, Hole) - there is no Bracket object, so the boolean never produced geometry. The exported "cut" body also has a bounding box of 46x36x30, which is larger than its own source box (40x30x20) - geometrically impossible for a subtraction.
Defect B - even when the geometry is right, the export is polluted.
On the PartDesign path the geometry IS computed correctly (the engine reports Body.Shape.Volume = 21738.05 mm^3, exactly the expected 24000 - pi6^220). But the exported STL has 1052 triangles / ~67478 mm^3 of mesh volume, versus 520 triangles / 21738.99 mm^3 when only the Body is exported. The extra geometry is the intermediate feature solids, exported as additional overlapping shells.
Neither defect is visible from the exit code, the file size, or the container metadata - only measuring the geometry reveals them.
Relevant Logs / Tracebacks
# --- Defect A: the harness output looks completely healthy ---
$ cli-anything-freecad -p demo.FCStd part boolean cut 0 1 -n Bracket
Boolean cut: Bracket
id: 3 name: Bracket type: cut params: {"base_id": 1, "tool_id": 2}
$ cli-anything-freecad -p demo.FCStd export render bracket.stl -p stl --overwrite
Exported: ...\bracket.stl
format: stl file_size: 25684 method: freecad-headless
# --- Defect A: control experiment - identical solids, NO boolean performed ---
bracket.stl sha256 f9c80c5e84fec914... 512 triangles 25684 bytes bbox 46x36x30
control.stl sha256 f9c80c5e84fec914... 512 triangles 25684 bytes bbox 46x36x30
-> byte-for-byte identical
# --- Defect A: what the engine actually sees inside the document ---
$ freecadcmd inspect_fc.py
real_demo.FCStd
Base Part::Box V = 24000.00 mm3 A = 5200.00 mm2 bbox=40.0x30.0x20.0
Hole Part::Cylinder V = 3392.92 mm3 A = 1357.17 mm2 bbox=12.0x12.0x30.0
(there is no Bracket object)
# --- Defect B: the engine says the PartDesign body is correct ... ---
real_pd.FCStd
Body PartDesign::Body V = 21738.05 mm3 bbox=40.0x30.0x20.0
Feature_additive_box PartDesign::AdditiveBox V = 24000.00 mm3 bbox=40.0x30.0x20.0
Feature_subtractive_cylinder PartDesign::SubtractiveCylinder V = 21738.05 mm3 bbox=40.0x30.0x20.0
# --- Defect B: ... but the harness's exported mesh disagrees ---
file triangles bytes mesh volume (mm3)
harness pd.stl 1052 52684 67477.98 <- polluted
body-only export (reference) 520 26084 21738.99 <- matches engine
control pdctl.stl 24 1284 48000.00 <- exactly 2x the 24000 box
Additional Context
Root cause (reading utils/freecad_macro_gen.py at commit 810c18b):
_gen_parts()only emits primitives. A part whose type is "cut" (which is whatpart booleanstores) falls through to:lines.append(f"# WARNING: Unknown part type '{part_type}' for '{name}'")so no boolean object is ever added to the FreeCAD document.
_gen_boolean_ops()iteratesproject.get("boolean_ops", []), butpart booleanrecords its result inparts(with type "cut"). The two never meet.The exporter - used by both the
partandbodypaths - collects every valid shape:for obj in doc.Objects: if hasattr(obj, 'Shape') and obj.Shape.isValid(): export_objects.append(obj)so intermediate feature solids are exported alongside the final result.
Three secondary observations from the same session (minor, but they cost time):
setup.py calls open("cli_anything/freecad/README.md") without encoding=. On a Chinese-locale Windows (default GBK) this raises UnicodeDecodeError, so the package cannot be installed with the documented pip install -e . Workaround: set PYTHONUTF8=1.
document new -o demo.FCStdwrites the harness's JSON session to a file named .FCStd (it starts with '{', not 'PK'). FreeCAD cannot open it; the engine-level checks above required exporting a real document via-p fcstd.find_freecad() searches PATH and C:\Program Files\FreeCAD*, but not the registry and not other drives. Here FreeCAD lives at D:\Program Files\FreeCAD 1.1, so FREECAD_PATH was required. Probing the Windows uninstall registry keys would find it automatically.
Reproduction scripts used for the evidence above (read-only; none modify the repo): verify_stl.py - independent binary-STL parser (triangle count, volume, bbox) inspect_fc.py - opens a real FCStd with freecadcmd and prints each Shape's Volume / Area / BoundingBox (engine ground truth) export_body_only.py - exports only the PartDesign Body, as a clean reference
Scope note: only part add + part boolean, and body additive-box + body subtractive-cylinder were tested. sketch / assembly / fem / cam / mesh / motion / techdraw and the remaining PartDesign features were NOT exercised, so this should not be read as a general statement about those paths.
Happy to split this into two issues if that helps triage - Defect A and Defect B have different triggers and different fixes.
Source: HKUDS/CLI-Anything