[Bug]: a curve-sweep component with an attachment renders as a straight cylinder, and its swept geometry is emitted as dead code
Before submitting
- I searched existing issues and did not find a duplicate.
- I confirmed this is a reproducible behavior problem, not a request for help using the project.
What happened?
A component authored as primitive: "curve-sweep" with an explicit spine renders as a straight tapered cylinder when it also carries an attachment block. The generator emits the correct buildCurveSweepGeometry({...}) call with the authored spine, but that call never executes: curve-sweep is in ATTACHMENT_PRIMITIVES, so makeAttachmentEndpoint(attachment) returns non-null and the ternary takes the CylinderGeometry branch. The swept geometry is emitted as dead code.
I am filing this as a question rather than a straightforward bug, because test_attachment_does_not_override_primitive.py::test_every_attachment_primitive_still_takes_the_endpoint_path asserts this behaviour deliberately for all five members of ATTACHMENT_PRIMITIVES — so it looks intentional, and I did not want to "fix" a documented decision. But the outcome is the same failure the fix that added that gate describes in its own comment:
Deriving geometry from it unconditionally silently overrode the authored primitive: an ellipsoid head, a tapered-sweep ear and a swept tail all came out as cylinders between their two endpoints, the spec validated, the factory built, and the wrong shape shipped.
That reasoning applies to curve-sweep exactly as it does to the primitives the gate rescued. It is also the specific defect swept_arc_gate.py exists to catch:
A curve claim ("curled into a hook, not a straight cone") needs
swept_arc_gate.py: silhouette IoU passes a straight cone occupying roughly the right cells.
Note the asymmetry: tapered-sweep keeps its swept geometry (there is a dedicated test for it), while curve-sweep does not. If that difference is intended, it would be worth a comment saying why.
Reproduction steps
# 1. Author a component with primitive "curve-sweep", a geometryDescriptor spine, and an
# attachment block carrying parentSocket / localStart / localEnd / contactType /
# embedDepth / gapTolerance (the structural pass requires an attachment on child parts).
# 2. Generate the factory:
python3 forge/stage3_build/generate_threejs_factory.py spec.json --pass-id blockout --out model.ts
# 3. Both of these are present in the output -- the second one wins at runtime:
grep -n "buildCurveSweepGeometry\|makeAttachmentEndpoint(attachment_claw" model.ts
# const endpoint_claw_2 = makeAttachmentEndpoint(attachment_claw_2);
# ... ? new THREE.CylinderGeometry(...) : buildCurveSweepGeometry({"spine": [...]})Reference image or input characteristics
Hard-surface object (claw hammer), 512×512 PNG, front view. The claw is a curved hook that sweeps down and left — the single most identity-defining feature of the silhouette.
Relevant output and evidence
Runtime probe of the built model in headless Chromium — the component named "Curved claw", authored curve-sweep:
{
"name": "Curved claw",
"geometryType": "CylinderGeometry",
"params": { "radiusTop": 0.033, "radiusBottom": 0.06, "height": 0.284,
"radialSegments": 32, "heightSegments": 12 },
"vertices": 559
}In the side-by-side comparison sheet the reference claw is a curved hook and the render is a straight angled rod. diagnose_render.py scored silhouetteIoU 0.281 against the 0.85 threshold for that render, though proportion errors of mine contributed to that number too.
Suggested direction
If endpoint-derived geometry is meant for axial members, curve-sweep may not belong in ATTACHMENT_PRIMITIVES — its whole purpose is a spine that is not a straight line between two endpoints. If it does belong there, then emitting the buildCurveSweepGeometry call as unreachable code is misleading; refusing at spec time, or dropping the dead branch, would at least make the choice visible instead of shipping a silently wrong shape.
Happy to send a PR either way once you say which behaviour you want.
Environment
- Windows 11, Python 3.13.14, three r180, headless Chromium (SwiftShader)
- Commit: 441af85
Source: img2threejs/img2threejs