- โ 5M-triangle safety cap hit during subdivision โ result may still be coarser than requested edge length.
+ โ 10M-triangle safety cap hit during subdivision โ result may still be coarser than requested edge length.
diff --git a/js/i18n.js b/js/i18n.js
index 47ddb64..dc39a6d 100644
--- a/js/i18n.js
+++ b/js/i18n.js
@@ -95,7 +95,7 @@ export const TRANSLATIONS = {
'tooltips.resolution': 'Edges longer than this value will be split during export',
'labels.outputTriangles': 'Output Triangles',
'tooltips.outputTriangles': 'Mesh is fully subdivided first, then decimated down to this count',
- 'warnings.safetyCapHit': '\u26a0 5M-triangle safety cap hit during subdivision \u2014 result may still be coarser than requested edge length.',
+ 'warnings.safetyCapHit': '\u26a0 10M-triangle safety cap hit during subdivision \u2014 result may still be coarser than requested edge length.',
'ui.exportStl': 'Export STL',
// Export progress stages
@@ -115,6 +115,10 @@ export const TRANSLATIONS = {
'sponsor.dontShow': "Don\u2019t show this again",
'sponsor.closeAndContinue':'Close & Continue',
+ // Store CTA
+ 'cta.store': '\uD83D\uDED2 Enjoying this free tool? Support us & shop at CNCKitchen.STORE!',
+ 'cta.storeDismiss': 'Dismiss',
+
// Alerts
'alerts.loadFailed': 'Could not load STL: {msg}',
'alerts.exportFailed': 'Export failed: {msg}',
@@ -132,7 +136,7 @@ export const TRANSLATIONS = {
// Viewport footer
'ui.wireframe': 'Drahtgitter',
- 'ui.controlsHint': 'Links ziehen: Drehen \u00a0ยท\u00a0 Rechts ziehen: Verschieben \u00a0ยท\u00a0 Scrollen: Zoomen',
+ 'ui.controlsHint': 'Linke Maustaste: Drehen \u00a0ยท\u00a0 Rechte Maustaste: Verschieben \u00a0ยท\u00a0 Mausrad: Zoomen',
'ui.meshInfo': '{n} Dreiecke ยท {mb} MB',
// Load STL button
@@ -165,7 +169,7 @@ export const TRANSLATIONS = {
'tooltips.proportionalScalingAria': 'Proportionale Skalierung (U = V)',
// Displacement section
- 'sections.displacement': 'Verschiebung',
+ 'sections.displacement': 'Texturtiefe',
'labels.amplitude': 'Amplitude',
// Surface mask section
@@ -213,7 +217,7 @@ export const TRANSLATIONS = {
'tooltips.resolution': 'Kanten l\u00e4nger als dieser Wert werden beim Export unterteilt',
'labels.outputTriangles': 'Max Dreiecke',
'tooltips.outputTriangles': 'Das Netz wird zuerst vollst\u00e4ndig unterteilt, dann auf diese Anzahl dezimiert',
- 'warnings.safetyCapHit': '\u26a0 5-Mio.-Dreiecke-Sicherheitsgrenze bei der Unterteilung erreicht \u2014 Ergebnis kann gr\u00f6ber als gew\u00fcnschte Kantenl\u00e4nge sein.',
+ 'warnings.safetyCapHit': '\u26a0 10-Mio.-Dreiecke-Sicherheitsgrenze bei der Unterteilung erreicht \u2014 Ergebnis kann gr\u00f6ber als gew\u00fcnschte Kantenl\u00e4nge sein.',
'ui.exportStl': 'STL exportieren',
// Export progress stages
@@ -233,6 +237,10 @@ export const TRANSLATIONS = {
'sponsor.dontShow': 'Nicht mehr anzeigen',
'sponsor.closeAndContinue':'Schlie\u00dfen & Weiter',
+ // Store CTA
+ 'cta.store': '\uD83D\uDED2 Dieses Tool ist kostenlos - schau deshalb mal bei CNCKitchen.STORE vorbei!',
+ 'cta.storeDismiss': 'Ausblenden',
+
// Alerts
'alerts.loadFailed': 'STL konnte nicht geladen werden: {msg}',
'alerts.exportFailed': 'Export fehlgeschlagen: {msg}',
diff --git a/js/main.js b/js/main.js
index 10df1b5..30a357a 100644
--- a/js/main.js
+++ b/js/main.js
@@ -300,14 +300,14 @@ function wireEvents() {
linkSlider(offsetVSlider, offsetVVal, v => { settings.offsetV = v; return v.toFixed(2); });
linkSlider(rotationSlider, rotationVal, v => { settings.rotation = v; return Math.round(v); });
linkSlider(amplitudeSlider, amplitudeVal, v => { settings.amplitude = v; return v.toFixed(2); });
- linkSlider(refineLenSlider, refineLenVal, v => { settings.refineLength = v; return v.toFixed(1); }, false);
+ linkSlider(refineLenSlider, refineLenVal, v => { settings.refineLength = v; return v.toFixed(2); }, false);
linkSlider(maxTriSlider, maxTriVal, v => { settings.maxTriangles = v; return formatM(v); }, false);
linkSlider(bottomAngleLimitSlider, bottomAngleLimitVal, v => { settings.bottomAngleLimit = v; return v; });
linkSlider(topAngleLimitSlider, topAngleLimitVal, v => { settings.topAngleLimit = v; return v; });
// โโ Export โโ
exportBtn.addEventListener('click', () => {
- if (localStorage.getItem('stlt-no-sponsor') === '1') {
+ if (sessionStorage.getItem('stlt-no-sponsor') === '1') {
handleExport();
return;
}
@@ -318,7 +318,7 @@ function wireEvents() {
const dismiss = () => {
if (document.getElementById('sponsor-dont-show').checked) {
- localStorage.setItem('stlt-no-sponsor', '1');
+ sessionStorage.setItem('stlt-no-sponsor', '1');
}
overlay.classList.add('hidden');
handleExport();
@@ -722,9 +722,9 @@ async function handleSTL(file) {
settings.offsetV = 0; resetVal(offsetVSlider, offsetVVal, 0);
triLimitWarning.classList.add('hidden');
- // Default edge length = 1/100 of the largest bounding box dimension
+ // Default edge length = 1/200 of the largest bounding box dimension
const maxDim = Math.max(bounds.size.x, bounds.size.y, bounds.size.z);
- const defaultEdge = Math.max(0.1, Math.min(5.0, +(maxDim / 100).toFixed(2)));
+ const defaultEdge = Math.max(0.05, Math.min(5.0, +(maxDim / 200).toFixed(2)));
settings.refineLength = defaultEdge;
refineLenSlider.value = defaultEdge;
refineLenVal.value = defaultEdge;
diff --git a/js/subdivision.js b/js/subdivision.js
index f057cda..095a9ee 100644
--- a/js/subdivision.js
+++ b/js/subdivision.js
@@ -15,7 +15,7 @@
import * as THREE from 'three';
const QUANTISE = 1e4;
-const SAFETY_CAP = 5_000_000; // absolute OOM guard
+const SAFETY_CAP = 10_000_000; // absolute OOM guard
// โโ Public entry point โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
diff --git a/js/viewer.js b/js/viewer.js
index c4e9a97..f018d14 100644
--- a/js/viewer.js
+++ b/js/viewer.js
@@ -7,6 +7,7 @@ import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
let renderer, camera, scene, controls, meshGroup, ambientLight, dirLight1, dirLight2, grid;
let currentMesh = null;
let axesGroup = null;
+let dimensionGroup = null;
let wireframeLines = null; // LineSegments overlay, or null when hidden
let wireframeVisible = false;
let exclusionMesh = null; // flat orange overlay for user-excluded faces
@@ -63,6 +64,81 @@ function buildAxesIndicator(size) {
return group;
}
+// Create a canvas-texture sprite label for a dimension annotation.
+// Flat ground-plane label โ no billboard, no background, lies directly on the bed.
+function buildDimensionLabel(text, hex, worldW, worldH) {
+ const c = document.createElement('canvas');
+ c.width = 256;
+ c.height = 64;
+ const ctx = c.getContext('2d');
+ ctx.clearRect(0, 0, 256, 64);
+ ctx.fillStyle = `#${hex.toString(16).padStart(6, '0')}`;
+ ctx.font = 'bold 36px Arial';
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(text, 128, 32);
+ const mesh = new THREE.Mesh(
+ new THREE.PlaneGeometry(worldW, worldH),
+ new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(c), transparent: true, depthTest: false, side: THREE.DoubleSide }),
+ );
+ mesh.renderOrder = 998;
+ return mesh;
+}
+
+// Build X/Y dimension-line annotations lying flat on the ground plane.
+function buildDimensions(box, groundZ, scale) {
+ const group = new THREE.Group();
+ const fmt = v => v.toFixed(2);
+ const pad = scale * 0.18;
+ const tick = scale * 0.08;
+ const lblW = scale * 0.50;
+ const lblH = scale * 0.12;
+ const zOff = 0.02; // tiny lift to avoid z-fighting with the grid
+
+ const addLine = (pts, hex) => {
+ const line = new THREE.Line(
+ new THREE.BufferGeometry().setFromPoints(pts),
+ new THREE.LineBasicMaterial({ color: hex, depthTest: false, transparent: true, opacity: 0.75 }),
+ );
+ line.renderOrder = 997;
+ group.add(line);
+ };
+
+ const addTick = (centre, dir, hex) => {
+ addLine([
+ centre.clone().addScaledVector(dir, -tick * 0.5),
+ centre.clone().addScaledVector(dir, tick * 0.5),
+ ], hex);
+ };
+
+ // X dimension โ line along the front edge of the model
+ {
+ const hex = 0xff3333;
+ const y = box.min.y - pad;
+ addLine([new THREE.Vector3(box.min.x, y, groundZ), new THREE.Vector3(box.max.x, y, groundZ)], hex);
+ addTick(new THREE.Vector3(box.min.x, y, groundZ), new THREE.Vector3(0, 1, 0), hex);
+ addTick(new THREE.Vector3(box.max.x, y, groundZ), new THREE.Vector3(0, 1, 0), hex);
+ const lbl = buildDimensionLabel(`X: ${fmt(box.max.x - box.min.x)}`, hex, lblW, lblH);
+ lbl.position.set((box.min.x + box.max.x) / 2, y - lblH * 0.7, groundZ + zOff);
+ group.add(lbl);
+ }
+
+ // Y dimension โ line along the right edge of the model
+ {
+ const hex = 0x33dd55;
+ const x = box.max.x + pad;
+ addLine([new THREE.Vector3(x, box.min.y, groundZ), new THREE.Vector3(x, box.max.y, groundZ)], hex);
+ addTick(new THREE.Vector3(x, box.min.y, groundZ), new THREE.Vector3(1, 0, 0), hex);
+ addTick(new THREE.Vector3(x, box.max.y, groundZ), new THREE.Vector3(1, 0, 0), hex);
+ const lbl = buildDimensionLabel(`Y: ${fmt(box.max.y - box.min.y)}`, hex, lblW, lblH);
+ lbl.position.set(x + lblH * 0.7, (box.min.y + box.max.y) / 2, groundZ + zOff);
+ lbl.rotation.z = Math.PI / 2;
+ group.add(lbl);
+ }
+
+ return group;
+}
+
export function initViewer(canvas) {
// Renderer
renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: false });
@@ -200,6 +276,11 @@ export function loadGeometry(geometry, material) {
const axisPad = axisSize * 1.8;
axesGroup.position.set(box.min.x - axisPad, box.min.y - axisPad, groundZ);
scene.add(axesGroup);
+
+ // Bounding-box dimension annotations on the ground plane
+ if (dimensionGroup) scene.remove(dimensionGroup);
+ dimensionGroup = buildDimensions(box, groundZ, sphere.radius);
+ scene.add(dimensionGroup);
}
/**
diff --git a/style.css b/style.css
index af4fa5d..cc1fdcc 100644
--- a/style.css
+++ b/style.css
@@ -219,6 +219,41 @@ main {
inset: 0;
}
+#store-cta-wrapper {
+ position: absolute;
+ bottom: 14px;
+ right: 14px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ z-index: 20;
+ pointer-events: none;
+ /* relative so the dismiss button can be positioned against the anchor */
+}
+#store-cta {
+ display: block;
+ position: relative;
+ padding: 10px 18px;
+ background: var(--accent);
+ color: #fff;
+ font-size: 14px;
+ font-weight: 700;
+ text-decoration: none;
+ border-radius: 8px;
+ box-shadow: none;
+ pointer-events: all;
+ white-space: nowrap;
+ transition: background 0.15s, transform 0.15s;
+ letter-spacing: 0.01em;
+}
+#store-cta:hover {
+ background: var(--accent-hover);
+ transform: translateY(-1px);
+}
+.store-cta-hidden {
+ display: none !important;
+}
+
#viewport-footer {
height: 28px;
background: var(--surface);