You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The mesh size fields previously clamped to [0.5, 500] mm on every keystroke and started at a fixed 20 mm default. This made small parts unmeshable — a 1×1×1 mm cube could not be meshed at all because every legal setting was coarser than the part itself.
This PR removes the clamping, accepts any positive element size, and computes a fresh default for each import based on the geometry's volume and surface area, aiming at a target element count (50K). The suggestion is displayed alongside the model's bounding box and estimated element count, so users can make informed choices even with unbounded fields.
Key Changes
web/src/lib/meshSizing.ts (new)
measureTessellation(): Computes volume (via divergence theorem), surface area, and bounding box of the tessellated geometry
estimateElementCount(): Predicts Netgen's element count from a size using empirical coefficients (6 tets/h³ for volume, 2 triangles/h² for surface)
sizeFromMeasure(): Inverts the estimator via geometric bisection to find the size that hits the target count
suggestElementSizes(): Public entry point returning max/min size pair for a fresh import
formatElementSize(): Rounds to 3 significant digits for readable display
web/src/hooks/useMesh.ts
Changed maxElementSize and minElementSize from numbers to strings (no clamping on input)
Added DEFAULT_MAX_ELEMENT_SIZE and DEFAULT_MIN_ELEMENT_SIZE constants for the initial UI state
Added suggestion useMemo that recomputes when stepSurface changes (i.e., on import)
Added useEffect to apply the suggestion to both fields on import
Moved validation from input handlers to meshVolume(): checks that max is positive, min is non-negative, and min ≤ max
Returns geometryMeasure from the suggestion so the UI can display extent and estimate
web/src/components/panel/MeshPanel.tsx
Removed min/max/step attributes from the number inputs (fields now accept any positive value)
Changed input handlers to store raw text, not clamped numbers
Added geometry-extent section showing bounding box and estimated element count
Added title attributes explaining the unbounded fields
Added data-testid attributes for test selectors
web/src/components/panel/LeftPanel.module.css
Added .hint class for the extent/estimate display (small monospace text in muted color)
web/src/workers/solver.worker.ts
Moved validation of maxElementSize and minElementSize from the input layer to the worker entry point
Added explicit error messages for non-positive max size, invalid min size, and min > max
Clarified that any positive size is legal (KOF-222)
web/tests/mesh-size.spec.ts (new)
E2E tests covering: sub-millimetre sizes can be typed and kept, fresh imports are sized to geometry, invalid sizes are rejected with specific messages, the worker rejects non-positive sizes, and a real import is meshed at its suggested size
web/tests/test_mesh_sizing.mjs (new)
Unit tests for the estimator: measuring closed tessellations, empty geometry, hitting the target count across scales, the 1 mm cube case, scale invariance, open (surface-only) geometry, and error reporting for degenerate tessellations
Notable Implementation Details
No clamping on input: The old Math.max(0.5, …) and Math.min(…, 500) are gone. Fields store raw text and are validated only when meshing starts, so users can type any value and see it reflected immediately.
Geometry-driven defaults: Each import computes its own suggestion from volume and surface area. A 1 mm cube gets ~0.05 mm elements (10+ per side); a 2 m
The mesh size fields clamped to [0.5, 500] mm on every keystroke and
started at a fixed 20 mm. On a 1x1x1 mm part every setting the UI allowed
was coarser than the part itself, so it could not be meshed at all.
Both fields now accept any positive size — they hold text and are parsed
and validated when meshing starts, so a value is rejected with a specific
message instead of being silently rewritten mid-typing. The worker
applies the same check, since it is driven directly by tests and
examples.
A fixed default has the same problem as a fixed range, so a fresh import
now starts from a size computed for its own geometry, aimed at ~50K
elements. The estimator predicts Netgen's count from the volume and the
boundary area of the tessellation the viewer already holds:
N ~ 6*V/h^3 + 2*A/h^2
The area term is what makes it work on thin-walled parts — sizing on
volume alone put the crane holder (160,000 mm2 of surface over
57,000 mm3) at 190K tets when 50K was asked for. Calibrated against
Netgen on test_files/, the sizes it picks land between 31K and 64K tets
across bulky, hollow and thin-walled geometry.
The panel shows the model extent and the element count the current size
implies, so an unbounded field is still an informed choice.
Fixes KOF-222
We reviewed changes in 303ba07...dce6328 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.
AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.
The reason will be displayed to describe this comment to others. Learn more.
use `Boolean((window as Window & { __kofem?: unknown }).__kofem)` instead
Prefer using explicit casts by calling Number, Boolean, or String over using operators like +, !! or "" +. This is considered best practice as it improves readability.
The reason will be displayed to describe this comment to others. Learn more.
Expected 'undefined' and instead saw 'void'
The void operator takes an operand and returns undefined. It can be used to ignore the value produced by an expression. However, this can lead to code that is difficult to understand and maintain. Historically, the void operator was used to get a "pure" undefined value, as the undefined variable was mutable prior to ES5.
The reason will be displayed to describe this comment to others. Learn more.
Block is redundant
In JavaScript, prior to ES6, standalone code blocks delimited by curly braces do not create a new scope and have no use. For example, these curly braces do nothing to foo:
Explicit Boolean() over !!, a redundant standalone block dropped, and the
element count read through one helper instead of a discarded expect.poll
return threaded past a void.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes KOF-222
The mesh size fields previously clamped to [0.5, 500] mm on every keystroke and started at a fixed 20 mm default. This made small parts unmeshable — a 1×1×1 mm cube could not be meshed at all because every legal setting was coarser than the part itself.
This PR removes the clamping, accepts any positive element size, and computes a fresh default for each import based on the geometry's volume and surface area, aiming at a target element count (50K). The suggestion is displayed alongside the model's bounding box and estimated element count, so users can make informed choices even with unbounded fields.
Key Changes
web/src/lib/meshSizing.ts (new)
measureTessellation(): Computes volume (via divergence theorem), surface area, and bounding box of the tessellated geometryestimateElementCount(): Predicts Netgen's element count from a size using empirical coefficients (6 tets/h³ for volume, 2 triangles/h² for surface)sizeFromMeasure(): Inverts the estimator via geometric bisection to find the size that hits the target countsuggestElementSizes(): Public entry point returning max/min size pair for a fresh importformatElementSize(): Rounds to 3 significant digits for readable displayweb/src/hooks/useMesh.ts
maxElementSizeandminElementSizefrom numbers to strings (no clamping on input)DEFAULT_MAX_ELEMENT_SIZEandDEFAULT_MIN_ELEMENT_SIZEconstants for the initial UI statesuggestionuseMemo that recomputes whenstepSurfacechanges (i.e., on import)meshVolume(): checks that max is positive, min is non-negative, and min ≤ maxgeometryMeasurefrom the suggestion so the UI can display extent and estimateweb/src/components/panel/MeshPanel.tsx
min/max/stepattributes from the number inputs (fields now accept any positive value)geometry-extentsection showing bounding box and estimated element countdata-testidattributes for test selectorsweb/src/components/panel/LeftPanel.module.css
.hintclass for the extent/estimate display (small monospace text in muted color)web/src/workers/solver.worker.ts
maxElementSizeandminElementSizefrom the input layer to the worker entry pointweb/tests/mesh-size.spec.ts (new)
web/tests/test_mesh_sizing.mjs (new)
Notable Implementation Details
Math.max(0.5, …)andMath.min(…, 500)are gone. Fields store raw text and are validated only when meshing starts, so users can type any value and see it reflected immediately.https://claude.ai/code/session_01XCr53GMR7xYgA51rfjgn8U