Add model statistics panel with dimensions and filament estimates#69
Add model statistics panel with dimensions and filament estimates#69RosalieWessels wants to merge 3 commits intoAdam-CAD:masterfrom
Conversation
|
@RosalieWessels is attempting to deploy a commit to the Adam Team on Vercel. A member of the Team first needs to authorize it. |
Greptile OverviewGreptile SummaryAdds a statistics panel to the right sidebar displaying real-time model dimensions (X/Y/Z) and filament print estimates (volume, weight, cost) when viewing generated 3D models. Key Changes
Implementation QualityThe implementation properly handles indexed vs non-indexed geometry by converting to non-indexed before volume calculation, preventing the bug mentioned in previous review threads. The cancellation flag in the async Notes
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant ParametricEditor
participant OpenSCADViewer
participant STLLoader
participant meshUtils
participant DimensionsContext
participant ParameterSection
User->>ParametricEditor: Views 3D model page
ParametricEditor->>DimensionsContext: Wraps components with DimensionsProvider
Note over OpenSCADViewer: When scadCode changes
OpenSCADViewer->>OpenSCADViewer: compileScad(scadCode)
OpenSCADViewer->>OpenSCADViewer: Receives compiled Blob output
OpenSCADViewer->>STLLoader: output.arrayBuffer().then(parse)
STLLoader-->>OpenSCADViewer: Returns BufferGeometry
OpenSCADViewer->>OpenSCADViewer: geom.computeBoundingBox()
OpenSCADViewer->>meshUtils: calculateBoundingBox(geom.boundingBox)
meshUtils-->>OpenSCADViewer: {x, y, z} dimensions
OpenSCADViewer->>DimensionsContext: setDimensions({x, y, z})
OpenSCADViewer->>OpenSCADViewer: geom.center() - for display
OpenSCADViewer->>meshUtils: calculateMeshVolume(geom)
Note over meshUtils: Handles indexed geometry<br/>Uses signed tetrahedron method
meshUtils-->>OpenSCADViewer: volumeMm3
OpenSCADViewer->>meshUtils: calculateFilamentEstimates(volumeMm3)
Note over meshUtils: Converts to cm³<br/>Applies PLA density (1.24 g/cm³)<br/>Calculates cost ($20/kg)
meshUtils-->>OpenSCADViewer: {volumeCm3, weightGrams, costUSD}
OpenSCADViewer->>DimensionsContext: setFilamentEstimates(estimates)
DimensionsContext->>ParameterSection: Context updates trigger re-render
ParameterSection->>ParameterSection: Displays dimensions (X/Y/Z color-coded)
ParameterSection->>ParameterSection: Displays print estimates (volume, weight, cost)
ParameterSection-->>User: Shows statistics panel in sidebar
|
| /** | ||
| * Calculate the volume of a mesh from its BufferGeometry | ||
| * Uses the signed tetrahedron volume method | ||
| */ | ||
| function calculateMeshVolume(geometry: BufferGeometry): number { | ||
| const position = geometry.getAttribute('position'); | ||
| if (!position) return 0; | ||
|
|
||
| let volume = 0; | ||
| const vertices = position.array; | ||
|
|
||
| // For each triangle, calculate signed volume of tetrahedron with origin | ||
| for (let i = 0; i < position.count; i += 3) { | ||
| const v0x = vertices[i * 3]; | ||
| const v0y = vertices[i * 3 + 1]; | ||
| const v0z = vertices[i * 3 + 2]; | ||
|
|
||
| const v1x = vertices[(i + 1) * 3]; | ||
| const v1y = vertices[(i + 1) * 3 + 1]; | ||
| const v1z = vertices[(i + 1) * 3 + 2]; | ||
|
|
||
| const v2x = vertices[(i + 2) * 3]; | ||
| const v2y = vertices[(i + 2) * 3 + 1]; | ||
| const v2z = vertices[(i + 2) * 3 + 2]; | ||
|
|
||
| // Signed volume of tetrahedron formed by triangle and origin | ||
| volume += | ||
| (v0x * (v1y * v2z - v2y * v1z) - | ||
| v1x * (v0y * v2z - v2y * v0z) + | ||
| v2x * (v0y * v1z - v1y * v0z)) / | ||
| 6; | ||
| } | ||
|
|
||
| return Math.abs(volume); | ||
| } |
There was a problem hiding this comment.
Duplicate function - move to meshUtils.ts
This calculateMeshVolume function duplicates functionality that should be centralized in src/utils/meshUtils.ts alongside calculateFilamentEstimates. This improves maintainability and allows reuse in other parts of the codebase.
| /** | |
| * Calculate the volume of a mesh from its BufferGeometry | |
| * Uses the signed tetrahedron volume method | |
| */ | |
| function calculateMeshVolume(geometry: BufferGeometry): number { | |
| const position = geometry.getAttribute('position'); | |
| if (!position) return 0; | |
| let volume = 0; | |
| const vertices = position.array; | |
| // For each triangle, calculate signed volume of tetrahedron with origin | |
| for (let i = 0; i < position.count; i += 3) { | |
| const v0x = vertices[i * 3]; | |
| const v0y = vertices[i * 3 + 1]; | |
| const v0z = vertices[i * 3 + 2]; | |
| const v1x = vertices[(i + 1) * 3]; | |
| const v1y = vertices[(i + 1) * 3 + 1]; | |
| const v1z = vertices[(i + 1) * 3 + 2]; | |
| const v2x = vertices[(i + 2) * 3]; | |
| const v2y = vertices[(i + 2) * 3 + 1]; | |
| const v2z = vertices[(i + 2) * 3 + 2]; | |
| // Signed volume of tetrahedron formed by triangle and origin | |
| volume += | |
| (v0x * (v1y * v2z - v2y * v1z) - | |
| v1x * (v0y * v2z - v2y * v0z) + | |
| v2x * (v0y * v1z - v1y * v0z)) / | |
| 6; | |
| } | |
| return Math.abs(volume); | |
| } | |
| // Remove this function and import it from meshUtils.ts instead | |
| // Add to meshUtils.ts: | |
| // export function calculateMeshVolume(geometry: BufferGeometry): number { ... } |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/viewer/OpenSCADViewer.tsx
Line: 30:64
Comment:
Duplicate function - move to `meshUtils.ts`
This `calculateMeshVolume` function duplicates functionality that should be centralized in `src/utils/meshUtils.ts` alongside `calculateFilamentEstimates`. This improves maintainability and allows reuse in other parts of the codebase.
```suggestion
// Remove this function and import it from meshUtils.ts instead
// Add to meshUtils.ts:
// export function calculateMeshVolume(geometry: BufferGeometry): number { ... }
```
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.| export function calculateMeshVolume(geometry: THREE.BufferGeometry): number { | ||
| const position = geometry.getAttribute('position'); | ||
| if (!position) return 0; | ||
|
|
||
| let volume = 0; |
There was a problem hiding this comment.
[P1] calculateMeshVolume likely incorrect/0 for indexed or non-triangle geometries
calculateMeshVolume iterates position in groups of 3 vertices and ignores geometry.index. For indexed BufferGeometry (common after loader/optimizations), this will compute the wrong volume. It can also produce nonsense if position.count isn’t a multiple of 3 (non-triangle draw mode).
If STLLoader returns indexed geometry in some cases, estimates will silently be wrong. Consider handling geometry.index (iterate indices) or forcing geometry = geometry.toNonIndexed() before volume calc.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/utils/meshUtils.ts
Line: 8:12
Comment:
[P1] `calculateMeshVolume` likely incorrect/0 for indexed or non-triangle geometries
`calculateMeshVolume` iterates `position` in groups of 3 vertices and ignores `geometry.index`. For indexed `BufferGeometry` (common after loader/optimizations), this will compute the wrong volume. It can also produce nonsense if `position.count` isn’t a multiple of 3 (non-triangle draw mode).
If STLLoader returns indexed geometry in some cases, estimates will silently be wrong. Consider handling `geometry.index` (iterate indices) or forcing `geometry = geometry.toNonIndexed()` before volume calc.
How can I resolve this? If you propose a fix, please make it concise.| @@ -86,12 +94,31 @@ export function OpenSCADViewer() { | |||
| const geom = loader.parse(buffer); | |||
| geom.center(); | |||
| geom.computeVertexNormals(); | |||
There was a problem hiding this comment.
[P2] Missing cancellation/ordering guard for async output.arrayBuffer() path
The output.arrayBuffer().then(...) chain can race when output changes rapidly (e.g., fast recompiles): a slower previous promise can resolve after a newer compile and overwrite geometry/dimensions/estimates with stale values. Consider adding a cancellation flag or sequence id inside the effect so only the latest output updates state.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/viewer/OpenSCADViewer.tsx
Line: 89:96
Comment:
[P2] Missing cancellation/ordering guard for async `output.arrayBuffer()` path
The `output.arrayBuffer().then(...)` chain can race when `output` changes rapidly (e.g., fast recompiles): a slower previous promise can resolve after a newer compile and overwrite `geometry`/dimensions/estimates with stale values. Consider adding a cancellation flag or sequence id inside the effect so only the latest `output` updates state.
How can I resolve this? If you propose a fix, please make it concise.| geom.center(); | ||
| geom.computeVertexNormals(); | ||
| geom.computeBoundingBox(); | ||
|
|
||
| // Calculate and set dimensions | ||
| const box = geom.boundingBox; | ||
| if (box) { | ||
| setDimensions({ | ||
| x: Math.round((box.max.x - box.min.x) * 100) / 100, | ||
| y: Math.round((box.max.y - box.min.y) * 100) / 100, | ||
| z: Math.round((box.max.z - box.min.z) * 100) / 100, | ||
| }); | ||
| } |
There was a problem hiding this comment.
[P2] Dimensions computed after geom.center() will be centered extents, not original size
You call geom.center() before geom.computeBoundingBox() and then compute box.max - box.min. Centering translates geometry but shouldn’t change size, so this is probably fine; however it does mean the bounding box is for the centered geometry (useful) and can hide issues if later code expects pre-center bounds. If the intent is “model size regardless of centering”, computing the bounding box before centering makes the dependency clearer.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/viewer/OpenSCADViewer.tsx
Line: 95:107
Comment:
[P2] Dimensions computed after `geom.center()` will be centered extents, not original size
You call `geom.center()` before `geom.computeBoundingBox()` and then compute `box.max - box.min`. Centering translates geometry but shouldn’t change size, so this is probably fine; however it does mean the bounding box is for the centered geometry (useful) and can hide issues if later code expects pre-center bounds. If the intent is “model size regardless of centering”, computing the bounding box before centering makes the dependency clearer.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.
Additional Comments (1)
In Also appears to violate the “avoid casting” rule. Consider switching to a real runtime check (e.g., Context Used: Rule from Prompt To Fix With AIThis is a comment left during a code review.
Path: src/components/viewer/OpenSCADViewer.tsx
Line: 205:211
Comment:
[P1] `as` cast bypasses type safety and can misfire
In `FixWithAIButton`, `fixError?.(error as OpenSCADError)` relies on `error.name === 'OpenSCADError'` (a mutable string) rather than an actual type guard, which defeats the repo rule against casting and can call `fixError` with a non-`OpenSCADError` object.
Also appears to violate the “avoid casting” rule.
Consider switching to a real runtime check (e.g., `error instanceof OpenSCADError`) or refactoring the prop types so `FixWithAIButton` only receives an `OpenSCADError` when `fixError` is provided.
**Context Used:** Rule from `dashboard` - Avoid using 'as' type casting in TypeScript code. Find alternative approaches for type safety instea... ([source](https://app.greptile.com/review/custom-context?memory=096c10cb-12df-4744-903f-f7cad6ba6369))
How can I resolve this? If you propose a fix, please make it concise. |
| geom.center(); | ||
|
|
||
| // Calculate volume and filament estimates | ||
| const volumeMm3 = calculateMeshVolume(geom); |
There was a problem hiding this comment.
Consider calculating volume before centering for cleaner data flow
Volume is calculated after centering, which shouldn't mathematically change the result but does modify vertex positions. Computing volume before centering would avoid potential precision issues with centered coordinates.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/viewer/OpenSCADViewer.tsx
Line: 110:110
Comment:
Consider calculating volume before centering for cleaner data flow
Volume is calculated after centering, which shouldn't mathematically change the result but does modify vertex positions. Computing volume before centering would avoid potential precision issues with centered coordinates.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.| const PLA_DENSITY_G_CM3 = 1.24; | ||
| // Average PLA filament cost: ~$20/kg = $0.02/g | ||
| const FILAMENT_COST_PER_GRAM = 0.02; |
There was a problem hiding this comment.
Consider documenting that cost assumes 100% solid infill
Real 3D printing typically uses 10-20% infill, not 100%, plus support material and ~10% waste. The solid volume calculation will overestimate costs for most prints. Adding a comment about this assumption would help future maintainers.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/utils/meshUtils.ts
Line: 66:68
Comment:
Consider documenting that cost assumes 100% solid infill
Real 3D printing typically uses 10-20% infill, not 100%, plus support material and ~10% waste. The solid volume calculation will overestimate costs for most prints. Adding a comment about this assumption would help future maintainers.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.| <p className="mt-1 text-[10px] text-adam-text-secondary/60"> | ||
| PLA @ $20/kg | ||
| </p> |
There was a problem hiding this comment.
Consider clarifying that cost is based on solid (100% infill) model
Most prints use 10-20% infill, making actual cost much lower than shown. Something like "Based on solid model @ PLA $20/kg" would set better expectations.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/parameter/ParameterSection.tsx
Line: 225:227
Comment:
Consider clarifying that cost is based on solid (100% infill) model
Most prints use 10-20% infill, making actual cost much lower than shown. Something like "Based on solid model @ PLA $20/kg" would set better expectations.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
| import type { FilamentEstimates, BoundingBox } from '@/utils/meshUtils'; | ||
|
|
||
| export type { FilamentEstimates }; | ||
| export type Dimensions = BoundingBox; |
There was a problem hiding this comment.


Summary
Adds a statistics panel to the right sidebar that displays real-time model dimensions and print cost estimates when viewing generated 3D models.
The panel updates automatically based on the currently rendered mesh and provides quick, slicer-style feedback without leaving the viewer.
Features
Dimensions
Displays X, Y, Z dimensions (mm)
Color-coded to match 3D axes:
X: red
Y: green
Z: blue
Print Estimates
Implementation Details
More screenshots
Note
Medium Risk
Adds new client-side mesh analysis (bounding box + volume) wired through shared context; risk is moderate due to potential performance/accuracy issues on large or non-manifold meshes and new state flow between viewer and sidebar.
Overview
Adds a new model statistics readout (dimensions and basic PLA print estimates) to the right sidebar, rendered in
ParameterSectionwhen a compiled model is available.Introduces
DimensionsContext(provided byParametricEditor) and updatesOpenSCADViewerto compute bounding box dimensions, mesh volume, and derived filament/cost estimates from the compiled STL output, clearing these values when output is absent.meshUtilsgains helpers for volume calculation, bounding box extraction, and filament estimate math, andOpenSCADViewertightens error handling for the Fix with AI action and guards async parsing with a cancellation flag.Written by Cursor Bugbot for commit 4c3d778. This will update automatically on new commits. Configure here.