nx_wallstat.nx source
↩ module page · 141 lines · 5142 B
1// nx_wallstat.nx -- Analyzes mesh face thicknesses to detect dominant wall structures while avoiding false positives from surface roughness.
2// nx_wallstat.nx -- a WALL-THICKNESS STATISTIC THAT IS SAFE ON SCANNED / RECONSTRUCTED MESHES.
3//
4// THE HAZARD THIS EXISTS TO REMOVE (measured 2026-07-31): taking the MINIMUM of nx_meshthick over all faces
5// is correct on a clean closed CAD solid -- which is where it was gated -- but on a reconstructed surface it
6// reports the mesh's OWN ROUGHNESS, because a ray from one triangle hits another triangle of the SAME sheet.
7// On the flip-analysis question this arc exists to answer, that is a FALSE UNDERBUILT FLAG: a confident
8// number that looks like a thin wall and is really scanner noise. Measured min-wall was 64 on an open sheet
9// with no wall at all, and stayed 64 on shells of 120 AND 240 -- it tracked nothing.
10//
11// THE FIX IS THE STATISTIC, NOT THE RAY-CAST: a real wall shows up as a DOMINANT POPULATION of faces at one
12// thickness. Noise does not. So histogram the per-face thicknesses, find the dominant mode, and ★REFUSE to
13// emit a wall at all when no mode dominates -- fail loudly rather than return a plausible wrong number.
14// license_tier: ORIGINAL
15import "nx_meshthick.nx"
16
17const WS_BINS: i64 = 48
18const WS_DOMINANT: i64 = 18 // the mode cluster must hold >=18% of measured faces to count as a wall
19const WS_MINFACES: i64 = 12 // below this there is not enough evidence to call anything a wall
20
21// out4: [0]=mode thickness [1]=faces in the mode cluster [2]=faces measured [3]=cluster percent
22// returns 1 if a dominant wall was found, 0 if REFUSED (caller must not invent a number).
23func ws_wall(mesh: i64, out4: *i64, hist: *i64, vals: *i64) -> i64 {
24 let hd: *i64 = m3_hdr(mesh)
25 out4[0] = 0 - 1
26 out4[1] = 0
27 out4[2] = 0
28 out4[3] = 0
29 var n: i64 = 0
30 var lo: i64 = 0
31 var hi: i64 = 0
32 var f: i64 = 0
33 while f < hd[1] {
34 let th: i64 = mtk_face_thickness(mesh, f)
35 if th > 0 {
36 vals[n] = th
37 if n == 0 {
38 lo = th
39 hi = th
40 } else {
41 if th < lo { lo = th }
42 if th > hi { hi = th }
43 }
44 n = n + 1
45 }
46 f = f + 1
47 }
48 out4[2] = n
49 if n < WS_MINFACES { return 0 }
50 // âš THE HISTOGRAM RANGE MUST BE OUTLIER-ROBUST. Spanning raw min..max lets a few LONG rays -- shots that
51 // cross the whole part rather than measure a wall -- stretch the range so far that the real wall
52 // population is crushed into one or two bins and merges with the roughness. Measured: a 120 shell
53 // reported 84, and a 240 shell reported nothing at all. Sorting and binning the 5th..75th percentile
54 // puts the resolution where walls actually live.
55 var a: i64 = 1
56 while a < n {
57 let key: i64 = vals[a]
58 var b2: i64 = a - 1
59 var go: i64 = 1
60 while go == 1 {
61 if b2 >= 0 {
62 if vals[b2] > key {
63 vals[b2 + 1] = vals[b2]
64 b2 = b2 - 1
65 } else { go = 0 }
66 } else { go = 0 }
67 }
68 vals[b2 + 1] = key
69 a = a + 1
70 }
71 lo = vals[n / 20]
72 hi = vals[(n * 3) / 4]
73 if hi <= lo {
74 out4[0] = lo
75 out4[1] = n
76 out4[3] = 100
77 return 1
78 }
79 var b: i64 = 0
80 while b < WS_BINS {
81 hist[b] = 0
82 b = b + 1
83 }
84 var i: i64 = 0
85 while i < n {
86 if vals[i] >= lo {
87 if vals[i] <= hi {
88 var k: i64 = ((vals[i] - lo) * (WS_BINS - 1)) / (hi - lo)
89 if k < 0 { k = 0 }
90 if k >= WS_BINS { k = WS_BINS - 1 }
91 hist[k] = hist[k] + 1
92 }
93 }
94 i = i + 1
95 }
96 // dominant bin, then absorb its immediate neighbours (a real wall straddles a bin boundary)
97 var best: i64 = 0
98 var bi: i64 = 0
99 b = 0
100 while b < WS_BINS {
101 if hist[b] > best {
102 best = hist[b]
103 bi = b
104 }
105 b = b + 1
106 }
107 var lob: i64 = bi - 1
108 var hib: i64 = bi + 1
109 if lob < 0 { lob = 0 }
110 if hib >= WS_BINS { hib = WS_BINS - 1 }
111 var cluster: i64 = 0
112 b = lob
113 while b <= hib {
114 cluster = cluster + hist[b]
115 b = b + 1
116 }
117 let pct: i64 = (cluster * 100) / n
118 out4[1] = cluster
119 out4[3] = pct
120 // ★REFUSE when nothing dominates: a scattered distribution means there is no wall to report, and a
121 // number emitted here would be indistinguishable from a real measurement downstream.
122 if pct < WS_DOMINANT { return 0 }
123 // mode value = mean of the thicknesses inside the cluster
124 let clo: i64 = lo + ((hi - lo) * lob) / (WS_BINS - 1)
125 let chi: i64 = lo + ((hi - lo) * (hib + 1)) / (WS_BINS - 1)
126 var sum: i64 = 0
127 var cnt: i64 = 0
128 i = 0
129 while i < n {
130 if vals[i] >= clo {
131 if vals[i] <= chi {
132 sum = sum + vals[i]
133 cnt = cnt + 1
134 }
135 }
136 i = i + 1
137 }
138 if cnt == 0 { return 0 }
139 out4[0] = sum / cnt
140 return 1
141}