grid-apps-cmms/src/kiri/app/visuals.js

256 lines
7.6 KiB
JavaScript
Raw Normal View History

2025-12-24 23:38:22 -05:00
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { api } from './api.js';
import { consts } from '../core/consts.js';
2025-12-25 11:37:12 -05:00
import { settings } from './conf/manager.js';
2025-12-24 23:38:22 -05:00
import { space as SPACE } from '../../moto/space.js';
import STACKS from './stacks.js';
const { COLOR, VIEWS } = consts;
/**
* Constrain value to min/max bounds.
* @param {number} v - Value to constrain
* @param {number} min - Minimum value
* @param {number} max - Maximum value
* @returns {number} Constrained value
*/
2025-12-24 23:38:22 -05:00
function bound(v,min,max) {
return Math.max(min,Math.min(max,v));
}
/**
* Get unit scale factor for coordinate conversion.
* Returns 25.4 for CAM mode with inches, 1.0 otherwise (mm).
* @returns {number} Unit scale factor
*/
2025-12-24 23:38:22 -05:00
function unitScale() {
return api.mode.is_cam() && settings.ctrl().units === 'in' ? 25.4 : 1;
}
/**
* Update speed color legend for preview mode.
* Shows color-coded speed visualization in preview mode for FDM.
* Generates 20 color swatches from worker and displays in UI.
* Only visible in PREVIEW mode for FDM (not SLA/LASER) when enabled.
* Emits 'preview.speeds' event with min/max speeds.
* @param {number} [maxSpeed] - Maximum speed value
* @param {number} [minSpeed] - Minimum speed value
*/
2025-12-24 23:38:22 -05:00
function updateSpeeds(maxSpeed, minSpeed) {
const { ui } = api;
const viewMode = api.view.get();
ui.speeds.style.display =
maxSpeed &&
settings.mode !== 'SLA' &&
settings.mode !== 'LASER' &&
viewMode === VIEWS.PREVIEW &&
ui.showSpeeds.checked ? 'block' : '';
if (maxSpeed) {
const colors = [];
for (let i = 0; i <= maxSpeed; i += maxSpeed / 20) {
colors.push(Math.round(Math.max(i, 1)));
}
api.client.colors(colors, maxSpeed, speedColors => {
const list = [];
Object.keys(speedColors).map(v => parseInt(v)).sort((a, b) => b - a).forEach(speed => {
const color = speedColors[speed];
const hex = color.toString(16).padStart(6, 0);
const r = (color >> 16) & 0xff;
const g = (color >> 8) & 0xff;
const b = (color >> 0) & 0xff;
const style = `background-color:#${hex}`;
list.push(`<label style="${style}">${speed}</label>`);
});
ui.speedbar.innerHTML = list.join('');
});
api.event.emit('preview.speeds', {
min: minSpeed,
max: maxSpeed
});
}
}
/**
* Update slider visual position and sync settings.
* Updates slider thumb positions based on current range.
* Synchronizes configuration fields from range values.
*/
2025-12-24 23:38:22 -05:00
function updateSlider() {
const { lo, hi, max } = api.slider.getRange();
if (max > 0) {
api.slider.updatePosition(lo / max, hi / max);
}
api.conf.update_fields_from_range();
}
/**
* Set visible layer range and update visualization.
* Bounds values to valid range and updates slider.
* Triggers slice visualization update.
* @param {number} [h] - High layer (defaults to current hi)
* @param {number} [l] - Low layer (defaults to current lo)
*/
2025-12-24 23:38:22 -05:00
function setVisibleLayer(h, l) {
const { lo, hi, max } = api.slider.getRange();
h = h >= 0 ? h : hi;
l = l >= 0 ? l : lo;
const newHi = bound(h, 0, max);
const newLo = bound(l, 0, newHi);
api.slider.setRange(newLo, newHi); // Notify callbacks (STACKS, scene updates)
api.slider.showLabels();
showSlices();
}
/**
* Set wireframe rendering mode for all widgets.
* Updates 3D scene after applying to all widgets.
* @param {boolean} bool - Enable/disable wireframe
* @param {number} [color] - Optional wireframe color
* @param {number} [opacity] - Optional wireframe opacity
*/
2025-12-24 23:38:22 -05:00
function setWireframe(bool, color, opacity) {
api.widgets.each(function(w) { w.setWireframe(bool, color, opacity) });
SPACE.update();
}
/**
* Toggle or set edge rendering for all widgets.
* If bool.toggle is truthy, toggles the current state.
* Otherwise sets edge visibility to bool value.
* Persists state to local storage and updates scene.
* @param {boolean|object} bool - Enable/disable edges, or {toggle: true} to toggle
*/
2025-12-24 23:38:22 -05:00
function setEdges(bool) {
if (bool && bool.toggle) {
api.local.toggle('model.edges');
} else {
api.local.set('model.edges', bool);
}
bool = api.local.getBoolean('model.edges');
api.widgets.each(w => w.setEdges(bool));
SPACE.update();
}
/**
* Update slider maximum from stack height.
* Sets slider max to tallest stack layer - 1.
* If set=true or current hi exceeds new max, resets range to show top layer.
* @param {boolean} [set] - Force reset range to max
*/
2025-12-24 23:38:22 -05:00
function updateSliderMax(set) {
let max = STACKS.getRange().tallest - 1;
api.slider.setMax(max);
const { hi } = api.slider.getRange();
if (set || max < hi) {
api.slider.setRange(max, max);
api.slider.showLabels();
}
}
/**
* Hide slice visualization and restore widget rendering.
* Clears stack display, restores model opacity, disables wireframe.
*/
2025-12-24 23:38:22 -05:00
function hideSlices() {
STACKS.clear();
api.widgets.setOpacity(COLOR.model_opacity);
api.widgets.each(function(widget) {
widget.setWireframe(false);
});
}
/**
* Show or hide all widgets in 3D scene.
* @param {boolean} bool - True to show, false to hide
*/
2025-12-24 23:38:22 -05:00
function setWidgetVisibility(bool) {
api.widgets.each(w => {
if (bool) {
w.show();
} else {
w.hide();
}
});
}
/**
* Show slice layer visualization.
* Only works in SLICE/PREVIEW/ANIMATE views (not ARRANGE).
* Updates slider range to show specified layer(s).
* If no layer specified, shows current hi layer.
* Expands range downward if needed (newLo = min(layer, lo)).
* Updates slider position and triggers stack visualization.
* @param {number|string} [layer] - Layer to show (defaults to current hi)
2025-12-24 23:38:22 -05:00
*/
function showSlices(layer) {
const viewMode = api.view.get();
if (viewMode === VIEWS.ARRANGE) {
return;
}
showSlider();
const { lo, hi, max } = api.slider.getRange();
if (typeof(layer) === 'string' || typeof(layer) === 'number') {
layer = parseInt(layer);
} else {
layer = hi;
}
layer = bound(layer, 0, max);
const newLo = layer < lo ? layer : lo;
api.slider.setRange(newLo, layer);
api.slider.showLabels();
updateSlider();
// STACKS.setRange is called by slider callback
SPACE.update();
}
/**
* Show layer slider UI element.
*/
2025-12-24 23:38:22 -05:00
function showSlider() {
api.slider.show();
}
/**
* Hide layer slider and speed legend UI elements.
*/
2025-12-24 23:38:22 -05:00
function hideSlider() {
api.slider.hide();
api.ui.speeds.style.display = 'none';
}
/**
* Synchronize stack label visibility with saved preferences.
* Reads visibility state from settings.labels for current mode/view combination.
* Applies visibility to all stack labels.
*/
2025-12-24 23:38:22 -05:00
function updateStackLabelState() {
const settings = api.conf.get();
// match label checkboxes to preference
for (let label of api.stacks.getLabels()) {
let check = `${settings.mode}-${api.view.get()}-${label}`;
api.stacks.setVisible(label, settings.labels[check] !== false);
}
}
export const visuals = {
set_wireframe: setWireframe,
set_edges: setEdges,
2025-12-24 23:38:22 -05:00
unit_scale: unitScale,
update_speeds: updateSpeeds,
update_slider: updateSlider,
update_slider_max: updateSliderMax,
update_stack_labels: updateStackLabelState,
show_slices: showSlices,
hide_slices: hideSlices,
show_slider: showSlider,
hide_slider: hideSlider,
set_visible_layer: setVisibleLayer,
set_widget_visibility: setWidgetVisibility
};