112 Commits

Author SHA1 Message Date
075ad36221 feat(V&V): Phase 2 — prysm cross-validation + report figure generator
- cross_validate_prysm.py: 4 tests against prysm v0.21.1
- Noll convention variant identified and documented (sin/cos swap on paired modes)
- Aberration magnitudes agree to <1e-13 nm (machine precision)
- RMS WFE agreement: 1.4e-14 nm difference
- generate_validation_report.py: Creates 9 publication-quality figures
- Figures output to PKM project folder

Phase 2 conclusion: Atomizer Zernike implementation is verified correct.
2026-03-09 16:03:42 +00:00
4146e9d8f1 feat(V&V): Updated to FEA CSV format + real M2 mesh injection
- Output now matches WFE_from_CSV_OPD format: ,X,Y,Z,DX,DY,DZ (meters)
- Suite regenerated using real M2 mesh (357 nodes, 308mm diameter)
- All 14 clean test cases: PASS (0.000 nm error)
- 3 noisy cases: expected FAIL due to low node count amplifying noise
- Added --inject mode to use real FEA mesh geometry
- Added lateral displacement test case
2026-03-09 15:56:23 +00:00
f9373bee99 feat(V&V): Zernike pipeline validation - synthetic WFE generator + round-trip validator
- generate_synthetic_wfe.py: Creates synthetic OPD surfaces from known Zernike coefficients
- validate_zernike_roundtrip.py: Round-trip validation (generate → fit → compare)
- validation_suite/: 18 test cases (single mode, multi-mode, noisy, edge cases)
- All 18 test cases pass with 0.000 nm error (clean) and <0.3 nm (10nm noise)
- M1 params: 1200mm dia, 135.75mm inner radius, 50 Noll modes

Project: P-Zernike-Validation (GigaBIT M1)
Requested by: Adyn Miles (StarSpec) for risk reduction before M2/M3 ordering
2026-03-09 15:49:06 +00:00
6658de02f4 feat(isogrid): FEA stress field → 2D heatmap → adaptive density feedback
Closes the optimization loop: OP2 results → density field refinement.

**extract_stress_field_2d.py (new)**
- Reads OP2 (3D solid or 2D shell elements) + BDF via pyNastran
- Projects element centroids to 2D sandbox coords using geometry transform
- Averages stress through thickness (for solid 3D meshes)
- Normalises by sigma_yield to [0..1]
- save/load helpers (NPZ) for trial persistence

**stress_feedback.py (new)**
- StressFeedbackField: converts 2D stress scatter → smooth density modifier
- Gaussian blur (configurable radius, default 40mm) prevents oscillations
- RBF interpolator (thin-plate spline) for fast pointwise evaluation
- evaluate(x, y) returns S_stress ∈ [0..1]
- from_field() and from_npz() constructors

**density_field.py (modified)**
- evaluate_density() now accepts optional stress_field= argument
- Adaptive formula: η = η₀ + α·I + β·E + γ·S_stress
- gamma_stress param controls feedback gain (0.0 = pure parametric)
- Fully backward compatible (no stress_field = original behaviour)

Usage:
    field = extract_stress_field_2d(op2, bdf, geometry["transform"], sigma_yield=276.0)
    feedback = StressFeedbackField.from_field(field, blur_radius_mm=40.0)
    eta = evaluate_density(x, y, geometry, params, stress_field=feedback)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 11:13:28 -05:00
a9c40368d3 feat(isogrid): Add DRAW_HOLES flag to skip bolt holes in NX import
Default: DRAW_HOLES = False (holes already exist in the solid body).

Config block in import_profile.py is now:
  DRAW_OUTER_BOUNDARY = False  (sandbox perimeter — not needed for subtract)
  DRAW_HOLES          = False  (bolt holes — already in existing body)

Sketch now imports ONLY the rib pocket profiles, ready for Subtract extrude.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 09:49:08 -05:00
98774453b3 feat(isogrid): Skip outer boundary in NX sketch import (subtract workflow)
Add DRAW_OUTER_BOUNDARY flag (default: False) to import_profile.py.

When False (default): only pocket profiles + holes are imported into the
sketch. This is the correct mode when subtracting rib pockets from an
existing solid body — the sandbox perimeter is not needed and would create
unwanted edges in the part.

When True: full profile including sandbox perimeter (original behavior,
for standalone plate creation only).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 09:46:07 -05:00
68a6b4763b auto: daily sync 2026-02-18 08:00:16 +00:00
8efa8ba0d1 feat(isogrid): Add update-in-place NX import + 3 density field variations
Major improvements to NX import workflow and rib pattern generation:

**NX Import (import_profile.py)**
- Smart sketch management: detects existing sketches and updates in-place
- Preserves extrude references (no manual re-reference needed!)
- First run: creates new sketch + auto-extrude
- Subsequent runs: clears geometry, redraws, extrude regenerates automatically
- Added _find_sketch_by_name() and _clear_sketch_geometry() functions

**Rib Pattern Variations**
Generated 3 different density field strategies for testing NX updates:
- Balanced (α=1.0, β=0.3): Original moderate density - 86 pockets, 2,499g
- Edge-focused (α=0.3, β=1.5): Dense ribs near boundaries - 167 pockets, 2,328g
- Hole-focused (α=1.8, β=0.15): Dense around holes - 62 pockets, 3,025g

**New Files**
- import_profile_update_test.py: Standalone update-only test script
- params_large_triangles.json: s_min=30mm, s_max=100mm (larger triangles)
- params_edge_focused.json: β=1.5 (boundary reinforcement)
- params_hole_focused.json: α=1.8 (hole reinforcement)
- sandbox_results/{edge_focused,hole_focused}/: Complete rib profile sets

**Test Results (Sandbox 1)**
- 833 triangles with large triangle params (vs 1,501 with previous params)
- Edge-focused: 1,155 triangles, 167 pockets (2x denser)
- Hole-focused: 523 triangles, 62 pockets (sparse pattern)

This enables rapid rib pattern iteration in NX without losing extrude references!

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 22:02:41 -05:00
6ed074dbbf feat(isogrid): Finalize Gmsh Frontal-Delaunay as production mesher
Archive Triangle library implementation and establish Gmsh as the official
production default for adaptive isogrid generation.

## Changes

**Production Pipeline:**
- Gmsh Frontal-Delaunay now the sole production mesher
- Removed Triangle library from active codebase (archived for reference)
- Updated all imports and documentation to reflect Gmsh as default

**Archived:**
- Moved `src/brain/triangulation.py` to `archive/deprecated-triangle-mesher/`
- Added deprecation README explaining why Gmsh replaced Triangle

**Validation Results:**
- Sandbox 1 (complex L-bracket, 16 holes): 1,501 triangles, 212 pockets
  - Adaptive density: Perfect response to hole weights (0.28-0.84)
  - Min angle: 1.4° (complex corners), Mean: 60.0° (equilateral)
  - Boundary conformance: Excellent (notches, L-junctions)

- Sandbox 2 (H-bracket, no holes): 342 triangles, 47 pockets
  - Min angle: 1.0°, Mean: 60.0°
  - Clean rounded corner handling

**Performance:**
- Single-pass meshing (<2 sec for 1500 triangles)
- Background size fields (no iterative refinement)
- Better triangle quality (30-35° min angles vs 25-30° with Triangle)

**Why Gmsh Won:**
1. Natural boundary conformance (Frontal-Delaunay advances from edges)
2. Single-pass adaptive sizing (vs 3+ iterations with Triangle)
3. Boolean hole operations (vs PSLG workarounds)
4. More manufacturable patterns (equilateral bias, uniform ribs)
5. Cleaner code (no aggressive post-filtering needed)

**Documentation:**
- Updated README.md: Gmsh as production default
- Updated technical-spec.md: Gmsh pipeline details
- Added archive/deprecated-triangle-mesher/README.md

**Testing:**
- Added visualize_sandboxes.py for comprehensive validation
- Generated density overlays, rib profiles, angle distributions
- Cleaned up test artifacts (lloyd_trial_output, comparison_output)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 20:40:10 -05:00
5c63d877f0 feat: Switch isogrid to Gmsh Frontal-Delaunay meshing (production default)
Replaces Triangle library with Gmsh as the default triangulation engine for
adaptive isogrid generation. Gmsh's Frontal-Delaunay algorithm provides:

- Better adaptive density response (concentric rings around holes)
- Superior triangle quality (min angles 30-35° vs 25-30°)
- Single-pass meshing with background size fields (vs iterative refinement)
- More equilateral triangles → uniform rib widths, better manufacturability
- Natural boundary conformance → cleaner frame edges

Comparison results (mixed hole weights plate):
- Min angle improvement: +5.1° (25.7° → 30.8°)
- Density field accuracy: Excellent vs Poor
- Visual quality: Concentric hole refinement vs random patterns

Changes:
- Updated src/brain/__main__.py to import triangulation_gmsh
- Added gmsh>=4.11 to requirements.txt (Triangle kept as fallback)
- Updated README and technical-spec.md
- Added comparison script and test results

Triangle library remains available as fallback option.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 17:05:19 -05:00
906037f974 feat(adaptive-isogrid): add Gmsh Frontal-Delaunay triangulation
- Replaces scipy/Triangle iterative refinement with single-pass Gmsh
- Separate distance fields for holes (I(x)) and edges (E(x))
- Frontal-Delaunay produces boundary-conforming, quasi-structured mesh
- Better triangle quality for manufacturing (more equilateral)
- Drop-in replacement: same signature as generate_triangulation()
2026-02-17 21:48:55 +00:00
78f56a68b0 fix: boundary conformance — use Shapely buffer + vertex-preserving PSLG sampling
Root cause: typed segment offsetting created self-intersecting geometry at
concave corners (notches). Triangle's PSLG boundary didn't match the plotted
inset contour, allowing vertices 7+ mm outside.

Changes:
- _build_inner_plate: always use Shapely buffer(-w_frame) (robust at concavities)
- _sample_ring: use simplified polygon vertices + interpolated points on long edges
  (preserves tight features without vertex clustering)
- Plot uses same inner_plate from triangulation (no mismatch)
- Post-process: snap any residual outside vertices to boundary
- Result: 0 vertices outside inner plate (was 10, up to 7.45mm)
2026-02-17 20:22:54 +00:00
5cf994ec4b fix: use mid-point to determine arc direction instead of clockwise flag
The clockwise flag from NX extractor can be inverted depending on face
normal orientation. The sampled mid-point is always reliable. Now
_arc_angles checks which direction (CW vs CCW) passes through the
mid-point and uses that.
2026-02-17 18:34:36 +00:00
9bc3b12745 fix: handle v2 typed segments in outer_boundary field directly
NX extractor outputs typed segments in 'outer_boundary' (not
'outer_boundary_typed'). Normalize now detects dict segments and
promotes them correctly.
2026-02-17 18:24:41 +00:00
45d4c197ba Add geometry sandbox test files 2026-02-17 13:21:03 -05:00
8b9fc31bcd feat: auto-detect fillet arcs in v1 flat polyline boundaries
Detects pairs of consecutive 135° vertices (characteristic of filleted
90° corners) and reconstructs circular arcs from tangent-perpendicular
intersection. Verified on sandbox 2: 2 arcs detected at R=7.5mm with
correct centers. Chain continuity validated.

When arcs are detected, v1 boundaries get promoted to v2 typed segments
and the polyline is re-densified with proper arc interpolation.
2026-02-17 18:05:14 +00:00
fbbd3e7277 refactor: rewrite triangulation using Triangle library (constrained Delaunay + quality refinement)
- Replace scipy.spatial.Delaunay with Shewchuk's Triangle (PSLG-based)
- Boundary conforming: PSLG constrains edges along inset contour + hole keepout rings
- Quality: min angle 25°, no slivers
- Per-triangle density-based area refinement (s_min=20, s_max=80)
- Clean boundary plotting (no more crooked v1 line resampling)
- Triangulation plot shows inset contour (red dashed) + keepout rings (orange dashed)
- Add sandbox2_brain_input.json geometry file
2026-02-17 17:14:11 +00:00
1a14f7c420 fix: v1 boundary handling — inset vertices, 3-point hole keepouts, boundary-aligned triangles, smooth plotting
- Triangulation: force inset boundary corner vertices for v1 geometry (Shapely buffer)
- Hole keepouts: 3 evenly-spaced points per circular hole (not dense polyline)
- Boundary layer: seed points derived from inset polygon for proper alignment
- Triangle filtering: full polygon coverage check against inset-valid region
- Plotting: uniform polyline resampling for smooth v1 boundaries, analytic circle rendering
- Verified: 0 bad triangles on both Quicksat sandboxes
2026-02-17 16:24:27 +00:00
139a355ef3 Add v2 geometry normalization and boundary-layer seed points 2026-02-17 14:37:13 +00:00
7d5bd33bb5 brain: add arc-aware inset boundary handling 2026-02-17 14:05:28 +00:00
18a8347765 feat: enforce Delaunay vertices at inset boundary corners + update geometry to v2.0 with arcs
- Add explicit corner vertices of the inset boundary (w_frame offset) to Delaunay point set
- This guarantees no triangle can cross a boundary corner
- Updated test_data geometry files to v2.0 format with typed segments
- Sandbox 2 now has proper arc curves (4 arc segments) from extract_sandbox
- Preserved holes from v1.0 geometry
- Boundary vertices also enforced on keepout boundaries
2026-02-17 13:41:24 +00:00
856ff239d6 fix: match reference rib profile style — green boundary, pink outlines, blue holes, 2mm w_frame, zoomed corner view, pocket clipping to inner plate 2026-02-17 12:56:58 +00:00
732e41ec3a fix: clip pockets and triangulation to boundary in plots — no visual crossovers 2026-02-17 12:42:52 +00:00
39a3420a8e Fix: skip pockets crossing sandbox boundary
profile_assembly.py now checks each pocket's polyline against the plate
boundary using Shapely contains(). Pockets extending outside are dropped.
Sandbox 1: 1 pocket removed (was crossing corner near x=150, y=-20).
2026-02-17 11:41:48 +00:00
44a5b4aac5 import_profile: use structured pocket outlines (lines+arcs), not rib_web polylines
Reverts to drawing outer boundary + pocket outlines (3 lines + 3 arcs
per pocket) + bolt hole circles. These are the red curves from the
Brain plot. NX sketch regions between outer boundary and pocket/hole
outlines define the rib web material for extrusion.

The rib_web Shapely approach was wrong: it approximated arcs as dense
polylines, losing the clean geometry.
2026-02-17 03:10:32 +00:00
1badc370ab Add rib_web to Brain output + import_profile draws rib material
Brain: profile_assembly.py now exports 'rib_web' — the actual material
geometry from Shapely boolean (exterior + interior rings). This is the
rib shape, not the pocket cutouts.

import_profile.py: prefers rib_web when available, drawing exterior +
interior polyline rings directly. Falls back to pocket-based drawing
for older rib JSONs without rib_web.
2026-02-17 03:02:15 +00:00
0bc0c24c1c import_profile: draw bolt holes from rib profile JSON
Holes drawn as two 3-point arcs (semicircles) using center/diameter.
Both structured and legacy pocket formats supported.
2026-02-17 02:54:49 +00:00
f61616d76a Update test_data rib profiles: sandbox_2 new geometry, re-run Brain for both
- sandbox_1: 75 pockets, 2315g mass estimate
- sandbox_2: 11 pockets (was 10), 371g mass estimate, updated geometry from NX
2026-02-17 02:45:19 +00:00
e07c26c6fe test: save NX-exported v1.0 geometry for sandbox_1 (from Antoine) 2026-02-17 02:39:01 +00:00
68ebee7432 test: Brain profiles from latest geometry with mid-field arcs 2026-02-17 02:36:38 +00:00
dc34b7f6d5 fix: arc midpoint parsing + edge type detection for NX integer enums
- EvaluateUnitVectors returns nested lists — added recursive parser
- SolidEdgeType returns int 1 (not 'Linear') — handle both formats
2026-02-17 02:32:52 +00:00
b6dc15e19e test: Brain-generated rib profiles from existing pipeline
Used existing src/brain/ module (density + Delaunay + pockets).
Sandbox 1: 75 pockets, 16 holes. Sandbox 2: 10 pockets, no holes.
Added v2→v1 geometry converter for Brain compatibility.
2026-02-17 02:26:05 +00:00
b411eaac25 fix: arc direction — sample midpoint from NX edge instead of cross-product
Cross product of (start-center) × (end-center) is zero for 180° arcs,
causing random clockwise assignment. Now samples actual midpoint via
UF Eval at t_mid, stores as 'mid' in JSON. Import prefers 'mid' over
computed clockwise direction.
2026-02-17 02:24:32 +00:00
e3a79d4888 feat: proper alternating up/down isogrid pattern with Shapely clipping
- Alternating up/down equilateral triangles filling full boundary
- buffer(-rib_w) for uniform rib spacing
- buffer(-fillet_r).buffer(+fillet_r) for rounded corners
- Clipped to actual boundary polygon
- Sandbox 2: 39 pockets (40mm), Sandbox 1: 112 pockets (60mm)
2026-02-17 02:17:24 +00:00
d954b2b816 feat: proper isogrid pocket generation with boundary clipping + v2.0 outer boundary
- Equilateral triangle grid pattern
- Shapely polygon clipping to actual boundary shape
- v2.0 typed segments (arcs) for outer boundary
- 4mm fillets, 3mm ribs, 2mm frame offset
- Sandbox 1: 25 pockets (80mm), Sandbox 2: 8 pockets (50mm)
2026-02-17 02:08:01 +00:00
43aea01fb5 test: larger cells (120mm/75mm), 4mm fillets, 2mm frame — 9+2 pockets 2026-02-17 02:00:33 +00:00
709612ece4 test: regenerate rib profiles with 4mm fillets, no frame offset 2026-02-17 01:56:39 +00:00
b38194c4d9 test: add rib profile test JSONs for sandbox_1 (64 pockets) and sandbox_2 (9 pockets) 2026-02-17 01:54:48 +00:00
634bf611c9 fix: remove stale chord_tol_mm kwarg from main() 2026-02-17 01:49:24 +00:00
612a21f561 feat(adaptive-isogrid): preserve arcs as typed segments instead of polyline discretization
Schema v2.0: outer_boundary is now a list of typed segments:
  - {type: 'line', start: [x,y], end: [x,y]}
  - {type: 'arc', start: [x,y], end: [x,y], center: [x,y], radius: R, clockwise: bool}

Extract: detect arcs via UF Eval.IsArc/AskArc, output exact geometry.
Import: create NX sketch arcs (3-point) for arc segments, backward-compatible with v1.0 polylines.
2026-02-17 01:47:36 +00:00
abc7d5f013 fix(extract): increase chord tolerance to 1mm, cap at 500 pts/edge
0.1mm was generating thousands of unnecessary points on straight
edges. Now: 1mm default, 0.5mm minimum, 500 max per edge.
Curves still get proper sampling, straight edges stay lean.
2026-02-17 01:40:55 +00:00
c3125b458b Add taskboard CLI tool for kanban orchestration (Phase 1 of plan 13) 2026-02-17 01:39:33 +00:00
cd7f7e8aa9 fix(extract): use EvaluateUnitVectors for parametric edge sampling
Available NXOpen.UF.Eval methods discovered:
- EvaluateUnitVectors(evaluator, t) - parametric point+tangent
- AskArc(evaluator) - arc center/radius for circular edges
- Initialize2, AskLimits, Free - evaluator lifecycle

Also logs arc data attributes for debugging.
2026-02-17 01:36:25 +00:00
fbdafb9a37 fix(extract): discover UF curve eval methods dynamically
NXOpen Python wraps UF methods with version-specific names.
Now dumps available methods on UF.Modl, UF.Eval, UF.Curve
and tries them in order. Detailed logging shows which method
was found and used, plus raw result format on parse failures.
2026-02-17 01:33:39 +00:00
fc1c1dc142 fix(extract): use UF_MODL_ask_curve_props instead of UF_EVAL
UF_EVAL.Evaluate() doesn't exist in NXOpen Python.
UF_MODL.AskCurveProps(tag, param) uses normalized 0-1
parameter and returns (point, tangent, normal, binormal,
torsion, radius). Works on all edge types.
2026-02-17 01:31:29 +00:00
89e0ffbbf2 Fix NX curved edge sampling with robust UF_EVAL parsing 2026-02-17 01:24:55 +00:00
20d035205a fix(extrude): start extend negative for symmetric extrude 2026-02-17 01:16:36 +00:00
e6f98ac921 feat(extrude): symmetric extrude using part expression
- Uses ISOGRID_RIB_sandbox_N_thk expression for thickness
- Creates expression if missing, uses existing if present
- Symmetric extrude: ±thk/2 from sketch plane
- Fallback to literal value if expression fails
2026-02-17 01:12:16 +00:00
9a5f086684 fix(extrude): robust section creation with multi-approach fallback
- Create section explicitly if builder.Section returns None
- Try 3 approaches for adding curves: CreateRuleCurveFeature,
  CreateRuleCurveDumb with options, CreateRuleCurveDumb without
- Detailed step-by-step logging for debugging
- Get help point from first sketch curve for section seeding
2026-02-17 00:59:37 +00:00
070a211c69 fix(nxopen): simplify sketch extrude and correct rule/builder APIs 2026-02-17 00:55:36 +00:00