* Smart resolution: pick a refineLength based on the active texture's detail * and the model's surface area, capped to fit the triangle budget. Run on * demand (button) so the result reflects the most up-to-date texture, mapping, * and geometry — i.e. the state the export pipeline will actually
()
| 3227 | exclThresholdRow.classList.add('hidden'); |
| 3228 | canvas.style.cursor = ''; |
| 3229 | setExclusionOverlay(null); |
| 3230 | setHoverPreview(null); |
| 3231 | _lastHoverTriIdx = -1; |
| 3232 | exclCount.textContent = t('excl.initExcluded'); |
| 3233 | // Build adjacency data for brush/bucket tools (synchronous; fast enough for |
| 3234 | // typical STL sizes processed by this tool) |
| 3235 | const adjData = buildAdjacency(currentGeometry); |
| 3236 | triangleAdjacency = adjData.adjacency; |
| 3237 | triangleCentroids = adjData.centroids; |
| 3238 | triangleFaceNormals = adjData.faceNormals; |
| 3239 | updateMeshDiagnostics(adjData, currentGeometry.attributes.position.count / 3); |
| 3240 | |
| 3241 | // Carry scale, offset, rotation, and all other tuning across model swaps — |
| 3242 | // they're normalized to the bounding box so they apply meaningfully to the |
| 3243 | // new mesh. Output resolution is the one exception: it's recomputed below |
| 3244 | // from the new model's diagonal so a default-sized edge length still makes |
| 3245 | // sense whether the user just loaded a thumb-sized part or a 1m piece. |
| 3246 | triLimitWarning.classList.add('hidden'); |
| 3247 | |
| 3248 | // Default edge length = 1/250 of the bounding box diagonal |
| 3249 | const diag = Math.sqrt(bounds.size.x ** 2 + bounds.size.y ** 2 + bounds.size.z ** 2); |
| 3250 | const defaultEdge = Math.max(0.05, Math.min(5.0, +(diag / 250).toFixed(2))); |
| 3251 | settings.refineLength = defaultEdge; |
| 3252 | refineLenSlider.value = defaultEdge; |
| 3253 | refineLenVal.value = defaultEdge; |
| 3254 | checkResolutionWarning(); |
| 3255 | |
| 3256 | const triCount = getTriangleCount(currentGeometry); |
| 3257 | const mb = ((currentGeometry.attributes.position.array.byteLength) / 1024 / 1024).toFixed(2); |
| 3258 | const sx = bounds.size.x.toFixed(2); |
| 3259 | const sy = bounds.size.y.toFixed(2); |
| 3260 | const sz = bounds.size.z.toFixed(2); |
| 3261 | _setMeshInfo(triCount, mb, sx, sy, sz); |
| 3262 | |
| 3263 | exportBtn.disabled = (activeMapEntry === null); |
| 3264 | export3mfBtn.disabled = (activeMapEntry === null); |
| 3265 | updateSmartResBtnState(); |
| 3266 | updatePreview(); |
| 3267 | } catch (err) { |
| 3268 | // A superseded STEP import (user dropped another file mid-tessellation) |
| 3269 | // is not a failure — the newer load owns the UI now. |
| 3270 | if (!err || !err.stepCancelled) { |
| 3271 | console.error('Failed to load model:', err); |
| 3272 | alert(t('alerts.loadFailed', { msg: err.message })); |
| 3273 | } |
nothing calls this directly
no test coverage detected