[Bug]: scaffolded transform.scale [1,1,1] silently discards authored dimensions, and --strict-quality still passes
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's authored dimensions are silently discarded whenever transform.scale is present — and new_sculpt_spec.py, this repo's own scaffold, writes "scale": [1, 1, 1] into every component it emits.
scale_vector() in forge/stage3_build/generate_threejs_factory.py:300 returns early on the mere presence of the key:
def scale_vector(component, transform):
if "scale" in transform:
return vector(transform.get("scale"), [1, 1, 1])
dimensions = component.get("dimensions")
...So the normal authoring flow — scaffold a spec, fill in the dimensions block the schema asks for and that --strict-quality checks the depth of — emits geometry.scale(1, 1, 1) for every part. Every component comes out unit-sized, and validate_sculpt_spec.py --strict-quality still returns PASS, because nothing anywhere compares the two fields.
I expected authored dimensions to reach the geometry, which is what the generator's own comment a few hundred lines down says is supposed to happen:
Every other primitive is authored unit-sized in
geometry_for(); its real dimensions are applied to the vertex data now, not to the pivot's.scale.
A wrong model behind a green gate is exactly the failure mode GeometryNotImplementedError in the same file was written to prevent ("Never silently substitute a box for this case — that produced structurally-wrong renders ... with no signal that anything was wrong").
Reproduction steps
# 1. Scaffold a spec the normal way and confirm what it writes:
python3 forge/stage2_spec/new_sculpt_spec.py "Scale Probe" --out spec.json
python3 -c "import json;d=json.load(open('spec.json'));print([c['transform'] for c in d['componentTree']])"
# -> [{'position': [0, 0, 0], 'rotation': [0, 0, 0], 'scale': [1, 1, 1]}]
# 2. Author a real componentTree on top of it: fill in per-component `dimensions`
# (e.g. a hammer head 0.52 x 0.22 x 0.20 and a handle 0.10 x 1.10 x 0.10),
# leaving the scaffold's transform block untouched.
# 3. The spec passes the strict gate:
python3 forge/stage2_spec/validate_sculpt_spec.py spec.json --strict-quality
# -> PASS
# 4. But every emitted part is unit-sized:
python3 forge/stage3_build/generate_threejs_factory.py spec.json --pass-id blockout --out model.ts
grep "Geometry.scale(" model.ts
# -> mesh_handle_0Geometry.scale(1.0, 1.0, 1.0);
# mesh_head_1Geometry.scale(1.0, 1.0, 1.0); ... etc, for all five
# 5. Delete `transform.scale` from each component and regenerate — dimensions now apply:
# -> mesh_handle_0Geometry.scale(0.1, 1.1, 0.1);
# mesh_head_1Geometry.scale(0.52, 0.22, 0.2);Reference image or input characteristics
Hard-surface object (claw hammer), 512×512 PNG, orthographic front view, flat-shaded. Not image-dependent — the defect is in codegen and reproduces from any spec that carries both fields.
Relevant output and evidence
Rendered in a browser at blockout with the generated factory. Before: five unit primitives stacked — a 1×1×1 box sitting on a 1×1×1 cylinder, no resemblance to the reference. After deleting transform.scale: correct proportions, recognisably the subject.
Both renders came from a spec that returned PASS from --strict-quality, which is the part that makes this worth fixing rather than documenting.
# runtime probe of the emitted model, before:
{"name": "Forged steel head", "geometryType": "BoxGeometry", "xSpread": 1.0, "ySpread": 1.0}
# after:
{"name": "Forged steel head", "geometryType": "BoxGeometry", "xSpread": 0.52, "ySpread": 0.22}Suggested fix
Treat an identity scale as carrying no authored information and fall through to dimensions. [1, 1, 1] multiplies by nothing, so deferring cannot change any render that did not already have this bug: where dimensions is absent or itself unit, the result is the same 1, 1, 1. A genuinely authored non-uniform scale must keep winning — test_hierarchy_scale.py builds a rig exactly that way (torso [3, 1, 0.2], no dimensions) and has to keep passing.
Environment
- Windows 11, Python 3.13.14
- Commit: 441af85
I have a fix and regression tests ready and will open a PR referencing this issue.
Source: img2threejs/img2threejs