fix: replace torus shapes with proper cylinder-based geometry

The torus primitive was creating "donut" shapes that looked unrealistic.
Fixed 7 parts by replacing torus with layered cylinders:

- flywheel: Now shows proper disc with bolt holes, no ring gear torus
- first-gear through fifth-gear: Tiered cylinders with splined hub
- release-bearing: Proper cylindrical bearing stack

Added torus detection to validate-geometry.ts:
- DONUT SHAPE: Warns when torus tubeR > 15% of body radius
- DOMINANT TORUS: Warns when torus radius close to body size

All 16 gearbox parts now pass validation with scores 84-162.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-21 22:25:49 +00:00
parent 0a7365f402
commit c3dca5ea64
8 changed files with 77 additions and 28 deletions

View File

@@ -34,9 +34,12 @@
{ "type": "circle", "r": 8, "fill": "#555" }
],
"geometry3d": [
{ "type": "cylinder", "r": 22, "height": 10, "fill": "#666" },
{ "type": "cylinder", "r": 16, "height": 6, "offsetZ": 2, "fill": "#777" },
{ "type": "cylinder", "r": 14, "height": 10, "subtract": true },
{ "type": "torus", "r": 20, "tubeR": 2, "offsetZ": 4, "fill": "#555" }
{ "type": "cylinder", "r": 24, "height": 10, "fill": "#606570" },
{ "type": "cylinder", "r": 22, "height": 8, "offsetY": 1, "fill": "#707580" },
{ "type": "cylinder", "r": 16, "height": 5, "fill": "#808590" },
{ "type": "cylinder", "r": 15, "height": 14, "fill": "#555560" },
{ "type": "cylinder", "r": 13, "height": 16, "subtract": true },
{ "type": "box", "width": 3, "height": 16, "depth": 28, "subtract": true },
{ "type": "box", "width": 28, "height": 16, "depth": 3, "subtract": true }
]
}

View File

@@ -33,9 +33,12 @@
{ "type": "circle", "r": 16, "fill": "#555" }
],
"geometry3d": [
{ "type": "cylinder", "r": 38, "height": 18, "fill": "#666" },
{ "type": "cylinder", "r": 32, "height": 14, "offsetZ": 2, "fill": "#777" },
{ "type": "cylinder", "r": 16, "height": 18, "subtract": true },
{ "type": "torus", "r": 36, "tubeR": 2, "offsetZ": 8, "fill": "#555" }
{ "type": "cylinder", "r": 40, "height": 18, "fill": "#606570" },
{ "type": "cylinder", "r": 38, "height": 16, "offsetY": 1, "fill": "#707580" },
{ "type": "cylinder", "r": 32, "height": 12, "fill": "#808590" },
{ "type": "cylinder", "r": 18, "height": 22, "fill": "#555560" },
{ "type": "cylinder", "r": 16, "height": 24, "subtract": true },
{ "type": "box", "width": 4, "height": 24, "depth": 34, "subtract": true },
{ "type": "box", "width": 34, "height": 24, "depth": 4, "subtract": true }
]
}

View File

@@ -41,9 +41,16 @@
{ "type": "text", "content": "DMF", "offsetY": 3, "fill": "#888", "fontSize": 10 }
],
"geometry3d": [
{ "type": "cylinder", "r": 55, "height": 25, "fill": "#555" },
{ "type": "cylinder", "r": 50, "height": 20, "offsetZ": 2.5, "fill": "#666" },
{ "type": "cylinder", "r": 12, "height": 25, "subtract": true },
{ "type": "torus", "r": 53, "tubeR": 3, "offsetZ": 10, "fill": "#444" }
{ "type": "cylinder", "r": 58, "height": 8, "fill": "#505560" },
{ "type": "cylinder", "r": 55, "height": 25, "fill": "#606570" },
{ "type": "cylinder", "r": 50, "height": 20, "offsetY": 2.5, "fill": "#707580" },
{ "type": "cylinder", "r": 25, "height": 30, "fill": "#555560" },
{ "type": "cylinder", "r": 15, "height": 35, "subtract": true },
{ "type": "cylinder", "r": 5, "height": 30, "offsetX": 35, "subtract": true },
{ "type": "cylinder", "r": 5, "height": 30, "offsetX": -35, "subtract": true },
{ "type": "cylinder", "r": 5, "height": 30, "offsetY": 35, "subtract": true },
{ "type": "cylinder", "r": 5, "height": 30, "offsetY": -35, "subtract": true },
{ "type": "cylinder", "r": 5, "height": 30, "offsetX": 25, "offsetY": 25, "subtract": true },
{ "type": "cylinder", "r": 5, "height": 30, "offsetX": -25, "offsetY": -25, "subtract": true }
]
}

View File

@@ -31,9 +31,12 @@
{ "type": "circle", "r": 10, "fill": "#555" }
],
"geometry3d": [
{ "type": "cylinder", "r": 26, "height": 12, "fill": "#666" },
{ "type": "cylinder", "r": 20, "height": 8, "offsetZ": 2, "fill": "#777" },
{ "type": "cylinder", "r": 16, "height": 12, "subtract": true },
{ "type": "torus", "r": 24, "tubeR": 2, "offsetZ": 5, "fill": "#555" }
{ "type": "cylinder", "r": 28, "height": 12, "fill": "#606570" },
{ "type": "cylinder", "r": 26, "height": 10, "offsetY": 1, "fill": "#707580" },
{ "type": "cylinder", "r": 20, "height": 6, "fill": "#808590" },
{ "type": "cylinder", "r": 18, "height": 16, "fill": "#555560" },
{ "type": "cylinder", "r": 16, "height": 18, "subtract": true },
{ "type": "box", "width": 4, "height": 18, "depth": 34, "subtract": true },
{ "type": "box", "width": 34, "height": 18, "depth": 4, "subtract": true }
]
}

View File

@@ -31,9 +31,10 @@
{ "type": "circle", "r": 10, "fill": "#2a2a2a" }
],
"geometry3d": [
{ "type": "cylinder", "r": 22, "height": 18, "fill": "#444" },
{ "type": "cylinder", "r": 18, "height": 12, "offsetZ": 3, "fill": "#555" },
{ "type": "cylinder", "r": 12, "height": 18, "subtract": true },
{ "type": "torus", "r": 15, "tubeR": 4, "fill": "#666" }
{ "type": "cylinder", "r": 24, "height": 12, "fill": "#505560" },
{ "type": "cylinder", "r": 22, "height": 18, "fill": "#606570" },
{ "type": "cylinder", "r": 20, "height": 14, "offsetY": 2, "fill": "#707580" },
{ "type": "cylinder", "r": 16, "height": 8, "offsetY": 7, "fill": "#808590" },
{ "type": "cylinder", "r": 14, "height": 20, "subtract": true }
]
}

View File

@@ -31,9 +31,12 @@
{ "type": "circle", "r": 14, "fill": "#555" }
],
"geometry3d": [
{ "type": "cylinder", "r": 34, "height": 16, "fill": "#666" },
{ "type": "cylinder", "r": 28, "height": 12, "offsetZ": 2, "fill": "#777" },
{ "type": "cylinder", "r": 16, "height": 16, "subtract": true },
{ "type": "torus", "r": 32, "tubeR": 2, "offsetZ": 7, "fill": "#555" }
{ "type": "cylinder", "r": 36, "height": 16, "fill": "#606570" },
{ "type": "cylinder", "r": 34, "height": 14, "offsetY": 1, "fill": "#707580" },
{ "type": "cylinder", "r": 28, "height": 10, "fill": "#808590" },
{ "type": "cylinder", "r": 18, "height": 20, "fill": "#555560" },
{ "type": "cylinder", "r": 16, "height": 22, "subtract": true },
{ "type": "box", "width": 4, "height": 22, "depth": 34, "subtract": true },
{ "type": "box", "width": 34, "height": 22, "depth": 4, "subtract": true }
]
}

View File

@@ -31,9 +31,12 @@
{ "type": "circle", "r": 12, "fill": "#555" }
],
"geometry3d": [
{ "type": "cylinder", "r": 30, "height": 14, "fill": "#666" },
{ "type": "cylinder", "r": 24, "height": 10, "offsetZ": 2, "fill": "#777" },
{ "type": "cylinder", "r": 16, "height": 14, "subtract": true },
{ "type": "torus", "r": 28, "tubeR": 2, "offsetZ": 6, "fill": "#555" }
{ "type": "cylinder", "r": 32, "height": 14, "fill": "#606570" },
{ "type": "cylinder", "r": 30, "height": 12, "offsetY": 1, "fill": "#707580" },
{ "type": "cylinder", "r": 24, "height": 8, "fill": "#808590" },
{ "type": "cylinder", "r": 18, "height": 18, "fill": "#555560" },
{ "type": "cylinder", "r": 16, "height": 20, "subtract": true },
{ "type": "box", "width": 4, "height": 20, "depth": 34, "subtract": true },
{ "type": "box", "width": 34, "height": 20, "depth": 4, "subtract": true }
]
}

View File

@@ -206,6 +206,32 @@ function detectBogusPatterns(geoms: Geometry3D[], errors: string[], warnings: st
warnings.push(`Shape [${i}] has very thin dimension (${minDim}mm) - may be invisible`)
}
})
// Pattern 9: Dominant torus (donut shape overwhelms part)
// A torus with tubeR > 10% of main body radius makes the part look like a donut
const torusShapes = geoms.filter(g => g.type === 'torus' && !g.subtract)
if (torusShapes.length > 0 && unionShapes.length > 0) {
const mainBody = unionShapes[0]
const mainDims = getShapeDimensions(mainBody)
const mainRadius = Math.max(...mainDims) / 2
torusShapes.forEach((torus) => {
const tubeR = torus.tubeR || 0
const torusR = torus.r || 0
// Warning if torus tube is thick relative to main body
if (tubeR > mainRadius * 0.15) {
const idx = geoms.indexOf(torus)
warnings.push(`DONUT SHAPE: Torus [${idx}] has thick tube (${tubeR}mm) relative to body (${mainRadius}mm radius) - part will look like a donut`)
}
// Warning if torus radius is close to or larger than main body
if (torusR > mainRadius * 0.9) {
const idx = geoms.indexOf(torus)
warnings.push(`DOMINANT TORUS: Torus [${idx}] radius (${torusR}mm) dominates body (${mainRadius}mm) - consider using cylinder rings instead`)
}
})
}
}
function getShapeBounds(geom: Geometry3D): { maxDim: number } {