Skip to content

KOF-222: Size mesh to geometry, not fixed defaults - #425

Merged
mkofler96 merged 2 commits into
mainfrom
claude/kofem-222-tzlri6
Aug 11, 2026
Merged

KOF-222: Size mesh to geometry, not fixed defaults#425
mkofler96 merged 2 commits into
mainfrom
claude/kofem-222-tzlri6

Conversation

@mkofler96

Copy link
Copy Markdown
Owner

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 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

https://claude.ai/code/session_01XCr53GMR7xYgA51rfjgn8U

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
@deepsource-io

deepsource-io Bot commented Aug 11, 2026

Copy link
Copy Markdown

DeepSource Code Review

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.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Coverage  

Code Review Summary

Analyzer Status Updated (UTC) Details
Code coverage Aug 11, 2026 6:25p.m. Review ↗
Rust Aug 11, 2026 6:25p.m. Review ↗
JavaScript Aug 11, 2026 6:25p.m. Review ↗
C & C++ Aug 11, 2026 6:25p.m. Review ↗

Code Coverage Summary

Language Line Coverage (New Code) Line Coverage (Overall)
Aggregate
95.3%
88%
[▲ up 0.2% from main]
Javascript
95.3%
88%
[▲ up 0.2% from main]

➟ Additional coverage metrics may have been reported. See full coverage report ↗


Important

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.

Comment thread web/tests/mesh-size.spec.ts Outdated

await page.goto("/app/", { waitUntil: "domcontentloaded" });
await page.waitForFunction(
() => !!(window as Window & { __kofem?: unknown }).__kofem,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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.

Comment thread web/tests/mesh-size.spec.ts Outdated
{ timeout: 240_000 },
)
.toBeGreaterThan(0);
void elements;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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.

Comment thread web/tests/test_mesh_sizing.mjs Outdated
Comment on lines +83 to +86
{
check("no points", measureTessellation([], []) === null);
check("no triangles", measureTessellation([[0, 0, 0]], []) === null);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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.
@mkofler96
mkofler96 merged commit 58c0d37 into main Aug 11, 2026
9 checks passed
@mkofler96
mkofler96 deleted the claude/kofem-222-tzlri6 branch August 11, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants