refactor color and opacity/edge management
This commit is contained in:
parent
a13bb79f4b
commit
9900161dcd
17 changed files with 795 additions and 64 deletions
|
|
@ -10,7 +10,7 @@ import web from '../../moto/webui.js';
|
||||||
import { beta, version } from '../../moto/license.js';
|
import { beta, version } from '../../moto/license.js';
|
||||||
import { broker } from '../../moto/broker.js';
|
import { broker } from '../../moto/broker.js';
|
||||||
import { client as workers } from './workers.js';
|
import { client as workers } from './workers.js';
|
||||||
import { consts, COLOR as color, LISTS as lists } from '../core/consts.js';
|
import { consts, LISTS as lists } from '../core/consts.js';
|
||||||
import { device, devices } from './devices.js';
|
import { device, devices } from './devices.js';
|
||||||
import { functions } from './function.js';
|
import { functions } from './function.js';
|
||||||
import { group as groupModule } from './groups.js';
|
import { group as groupModule } from './groups.js';
|
||||||
|
|
@ -113,7 +113,6 @@ export const api = {
|
||||||
.catch(err => console.error('Clipboard Error:', err));
|
.catch(err => console.error('Clipboard Error:', err));
|
||||||
},
|
},
|
||||||
clone: Object.clone,
|
clone: Object.clone,
|
||||||
color,
|
|
||||||
conf: settings.conf,
|
conf: settings.conf,
|
||||||
const: {
|
const: {
|
||||||
LANG, LOCAL, SETUP, SECURE, SPACE, STACKS,
|
LANG, LOCAL, SETUP, SECURE, SPACE, STACKS,
|
||||||
|
|
|
||||||
69
src/kiri/app/color/base.js
Normal file
69
src/kiri/app/color/base.js
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BASE_SCHEME: Shared colors used across all device modes
|
||||||
|
* All mode-specific schemes inherit from this base
|
||||||
|
*/
|
||||||
|
const BASE_SCHEME = {
|
||||||
|
// Widget colors for selected/deselected states
|
||||||
|
widget: {
|
||||||
|
selected: {
|
||||||
|
light: [ 0xbbff00, 0xbbee00, 0xbbdd00, 0xbb9900 ],
|
||||||
|
dark: [ 0xbbff00, 0xbbee00, 0xbbdd00, 0xbb9900 ]
|
||||||
|
},
|
||||||
|
deselected: {
|
||||||
|
light: [ 0xffff00, 0xffdd00, 0xffbb00, 0xff9900 ],
|
||||||
|
dark: [ 0xffff00, 0xffdd00, 0xffbb00, 0xff9900 ]
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
// Computed via avgc(0x888888, baseColor, 3)
|
||||||
|
mixWith: 0x888888,
|
||||||
|
mixRatio: 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Edge rendering settings
|
||||||
|
edges: {
|
||||||
|
color: {
|
||||||
|
light: 0x888888,
|
||||||
|
dark: 0x444444
|
||||||
|
},
|
||||||
|
angle: 20 // Default edge detection angle threshold
|
||||||
|
},
|
||||||
|
|
||||||
|
// Wireframe rendering settings
|
||||||
|
wireframe: {
|
||||||
|
color: {
|
||||||
|
light: 0x000000,
|
||||||
|
dark: 0xaaaaaa
|
||||||
|
},
|
||||||
|
opacity: {
|
||||||
|
light: 0.5,
|
||||||
|
dark: 0.25
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Grid colors for platform
|
||||||
|
grid: {
|
||||||
|
major: {
|
||||||
|
light: 0x999999,
|
||||||
|
dark: 0x666666
|
||||||
|
},
|
||||||
|
minor: {
|
||||||
|
light: 0xcccccc,
|
||||||
|
dark: 0x333333
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// View-specific opacity defaults (common across modes)
|
||||||
|
views: {
|
||||||
|
ARRANGE: {
|
||||||
|
model_opacity: 1.0
|
||||||
|
},
|
||||||
|
SLICE: {
|
||||||
|
slicing_opacity: 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { BASE_SCHEME };
|
||||||
58
src/kiri/app/color/cam.js
Normal file
58
src/kiri/app/color/cam.js
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
import { BASE_SCHEME } from './base.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CAM_SCHEME: Colors specific to CAM (3-axis milling/machining) mode
|
||||||
|
* Inherits from BASE_SCHEME and overrides/extends with CAM-specific values
|
||||||
|
*/
|
||||||
|
const CAM_SCHEME = {
|
||||||
|
// Inherit all base colors
|
||||||
|
...BASE_SCHEME,
|
||||||
|
|
||||||
|
// CAM-specific view overrides
|
||||||
|
views: {
|
||||||
|
...BASE_SCHEME.views,
|
||||||
|
SLICE: {
|
||||||
|
...BASE_SCHEME.views.SLICE,
|
||||||
|
sliced_opacity: 0.2 // CAM shows more during slice (vs FDM 0.0)
|
||||||
|
},
|
||||||
|
PREVIEW: {
|
||||||
|
preview: {
|
||||||
|
light: 0xdddddd,
|
||||||
|
dark: 0x888888
|
||||||
|
},
|
||||||
|
preview_opacity: {
|
||||||
|
light: 0.2,
|
||||||
|
dark: 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// CAM-specific operations
|
||||||
|
operations: {
|
||||||
|
// Tab visualization (persistent geometry)
|
||||||
|
tabs: {
|
||||||
|
color: {
|
||||||
|
light: 0x0000dd, // Blue in light mode
|
||||||
|
dark: 0x00ddff // Cyan in dark mode
|
||||||
|
},
|
||||||
|
opacity: {
|
||||||
|
light: 0.6,
|
||||||
|
dark: 0.75
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Gcode preview colors (same as FDM, but documented for CAM)
|
||||||
|
gcode: {
|
||||||
|
head: 0x888888,
|
||||||
|
move: {
|
||||||
|
light: 0xaaaaaa,
|
||||||
|
dark: 0x666666
|
||||||
|
},
|
||||||
|
print: 0x777700
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { CAM_SCHEME };
|
||||||
42
src/kiri/app/color/fdm.js
Normal file
42
src/kiri/app/color/fdm.js
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
import { BASE_SCHEME } from './base.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FDM_SCHEME: Colors specific to FDM (Fused Deposition Modeling) mode
|
||||||
|
* Inherits from BASE_SCHEME and overrides/extends with FDM-specific values
|
||||||
|
*/
|
||||||
|
const FDM_SCHEME = {
|
||||||
|
// Inherit all base colors
|
||||||
|
...BASE_SCHEME,
|
||||||
|
|
||||||
|
// FDM-specific view overrides
|
||||||
|
views: {
|
||||||
|
...BASE_SCHEME.views,
|
||||||
|
PREVIEW: {
|
||||||
|
preview_opacity: 0.0, // FDM hides widget in preview
|
||||||
|
sliced_opacity: 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// FDM-specific operations
|
||||||
|
operations: {
|
||||||
|
// Support painting overlay
|
||||||
|
paint: {
|
||||||
|
overlay: 0x4488ff, // Light blue for painted support regions
|
||||||
|
opacity: 0.7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Gcode preview colors
|
||||||
|
gcode: {
|
||||||
|
head: 0x888888,
|
||||||
|
move: {
|
||||||
|
light: 0xaaaaaa,
|
||||||
|
dark: 0x666666
|
||||||
|
},
|
||||||
|
print: 0x777700
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { FDM_SCHEME };
|
||||||
28
src/kiri/app/color/laser.js
Normal file
28
src/kiri/app/color/laser.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
import { BASE_SCHEME } from './base.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LASER_SCHEME: Colors for LASER, DRAG (knife), WJET (waterjet), and WEDM (wire-edm) modes
|
||||||
|
* All 2D device types share similar visual characteristics
|
||||||
|
* Inherits from BASE_SCHEME with minimal overrides
|
||||||
|
*/
|
||||||
|
const LASER_SCHEME = {
|
||||||
|
// Inherit all base colors
|
||||||
|
...BASE_SCHEME,
|
||||||
|
|
||||||
|
// Laser-specific view overrides (similar to CAM)
|
||||||
|
views: {
|
||||||
|
...BASE_SCHEME.views,
|
||||||
|
PREVIEW: {
|
||||||
|
preview_opacity: 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// DRAG, WJET, and WEDM all use the same scheme as LASER
|
||||||
|
const DRAG_SCHEME = LASER_SCHEME;
|
||||||
|
const WJET_SCHEME = LASER_SCHEME;
|
||||||
|
const WEDM_SCHEME = LASER_SCHEME;
|
||||||
|
|
||||||
|
export { LASER_SCHEME, DRAG_SCHEME, WJET_SCHEME, WEDM_SCHEME };
|
||||||
145
src/kiri/app/color/schemes.js
Normal file
145
src/kiri/app/color/schemes.js
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
import { FDM_SCHEME } from './fdm.js';
|
||||||
|
import { CAM_SCHEME } from './cam.js';
|
||||||
|
import { SLA_SCHEME } from './sla.js';
|
||||||
|
import { LASER_SCHEME, DRAG_SCHEME, WJET_SCHEME, WEDM_SCHEME } from './laser.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ColorSchemeRegistry: Central registry for all mode-specific color schemes
|
||||||
|
* Provides theme-aware color resolution for UI rendering
|
||||||
|
*/
|
||||||
|
class ColorSchemeRegistry {
|
||||||
|
constructor() {
|
||||||
|
// Map mode IDs to their schemes
|
||||||
|
this.schemes = {
|
||||||
|
1: FDM_SCHEME, // FDM mode
|
||||||
|
2: LASER_SCHEME, // LASER mode
|
||||||
|
3: CAM_SCHEME, // CAM mode
|
||||||
|
4: SLA_SCHEME, // SLA mode
|
||||||
|
5: DRAG_SCHEME, // DRAG knife mode
|
||||||
|
6: WJET_SCHEME, // Waterjet mode
|
||||||
|
7: WEDM_SCHEME // Wire-EDM mode
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the complete scheme for a given mode and theme
|
||||||
|
* @param {number|string} mode - Mode ID (1=FDM, 2=LASER, 3=CAM, etc) or name
|
||||||
|
* @param {string} theme - 'light' or 'dark'
|
||||||
|
* @returns {object} Resolved scheme object
|
||||||
|
*/
|
||||||
|
getScheme(mode, theme = 'light') {
|
||||||
|
// Convert mode name to ID if needed
|
||||||
|
if (typeof mode === 'string') {
|
||||||
|
mode = this._modeNameToId(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
const scheme = this.schemes[mode];
|
||||||
|
if (!scheme) {
|
||||||
|
console.warn(`Unknown mode: ${mode}, using FDM scheme as fallback`);
|
||||||
|
return this._resolveTheme(this.schemes[1], theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._resolveTheme(scheme, theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific color value from a scheme
|
||||||
|
* @param {number|string} mode - Mode ID or name
|
||||||
|
* @param {string} theme - 'light' or 'dark'
|
||||||
|
* @param {string} path - Dot-separated path (e.g., 'widget.selected', 'edges.color')
|
||||||
|
* @returns {*} Color value or object
|
||||||
|
*/
|
||||||
|
getColor(mode, theme, path) {
|
||||||
|
const scheme = this.getScheme(mode, theme);
|
||||||
|
return this._getNestedValue(scheme, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute disabled color by mixing base color with gray
|
||||||
|
* Implements: avgc(0x888888, baseColor, 3)
|
||||||
|
* @param {number} baseColor - Base color hex value
|
||||||
|
* @returns {number} Mixed color hex value
|
||||||
|
*/
|
||||||
|
computeDisabledColor(baseColor) {
|
||||||
|
// Extract RGB components
|
||||||
|
const r1 = (0x888888 >> 16) & 0xFF;
|
||||||
|
const g1 = (0x888888 >> 8) & 0xFF;
|
||||||
|
const b1 = 0x888888 & 0xFF;
|
||||||
|
|
||||||
|
const r2 = (baseColor >> 16) & 0xFF;
|
||||||
|
const g2 = (baseColor >> 8) & 0xFF;
|
||||||
|
const b2 = baseColor & 0xFF;
|
||||||
|
|
||||||
|
// Average with ratio 3:1 (3 parts gray, 1 part base)
|
||||||
|
const r = Math.floor((r1 * 3 + r2) / 4);
|
||||||
|
const g = Math.floor((g1 * 3 + g2) / 4);
|
||||||
|
const b = Math.floor((b1 * 3 + b2) / 4);
|
||||||
|
|
||||||
|
return (r << 16) | (g << 8) | b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve theme-specific values throughout the scheme
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_resolveTheme(scheme, theme) {
|
||||||
|
const resolved = JSON.parse(JSON.stringify(scheme)); // Deep clone
|
||||||
|
|
||||||
|
this._resolveThemeRecursive(resolved, theme);
|
||||||
|
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively resolve theme values in an object
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_resolveThemeRecursive(obj, theme) {
|
||||||
|
for (const key in obj) {
|
||||||
|
const value = obj[key];
|
||||||
|
|
||||||
|
if (value && typeof value === 'object') {
|
||||||
|
// Check if this object has light/dark properties
|
||||||
|
if ('light' in value && 'dark' in value) {
|
||||||
|
// Replace the object with the theme-specific value
|
||||||
|
obj[key] = value[theme];
|
||||||
|
} else {
|
||||||
|
// Recurse into nested objects
|
||||||
|
this._resolveThemeRecursive(value, theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get nested value from object using dot-separated path
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getNestedValue(obj, path) {
|
||||||
|
return path.split('.').reduce((current, key) => current?.[key], obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert mode name to ID
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_modeNameToId(name) {
|
||||||
|
const modes = {
|
||||||
|
'FDM': 1,
|
||||||
|
'LASER': 2,
|
||||||
|
'CAM': 3,
|
||||||
|
'SLA': 4,
|
||||||
|
'DRAG': 5,
|
||||||
|
'WJET': 6,
|
||||||
|
'WEDM': 7
|
||||||
|
};
|
||||||
|
return modes[name.toUpperCase()] || 1; // Default to FDM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create singleton instance
|
||||||
|
const colorSchemeRegistry = new ColorSchemeRegistry();
|
||||||
|
|
||||||
|
export { colorSchemeRegistry, ColorSchemeRegistry };
|
||||||
23
src/kiri/app/color/sla.js
Normal file
23
src/kiri/app/color/sla.js
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
import { BASE_SCHEME } from './base.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SLA_SCHEME: Colors specific to SLA (stereolithography/resin) mode
|
||||||
|
* Inherits from BASE_SCHEME with minimal overrides
|
||||||
|
*/
|
||||||
|
const SLA_SCHEME = {
|
||||||
|
// Inherit all base colors
|
||||||
|
...BASE_SCHEME,
|
||||||
|
|
||||||
|
// SLA-specific view overrides (similar to FDM)
|
||||||
|
views: {
|
||||||
|
...BASE_SCHEME.views,
|
||||||
|
PREVIEW: {
|
||||||
|
preview_opacity: 0.0,
|
||||||
|
sliced_opacity: 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { SLA_SCHEME };
|
||||||
|
|
@ -12,10 +12,20 @@ import { utils } from '../../core/utils.js';
|
||||||
import { version } from '../../../moto/license.js';
|
import { version } from '../../../moto/license.js';
|
||||||
import { JSZip } from '../../../ext/jszip-esm.js';
|
import { JSZip } from '../../../ext/jszip-esm.js';
|
||||||
|
|
||||||
|
import { colorSchemeRegistry } from '../color/schemes.js';
|
||||||
|
|
||||||
const { areEqual, ls2o, js2o } = utils;
|
const { areEqual, ls2o, js2o } = utils;
|
||||||
const { COLOR } = consts;
|
|
||||||
const { clone } = Object;
|
const { clone } = Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current color scheme
|
||||||
|
*/
|
||||||
|
function getColorScheme() {
|
||||||
|
const mode = api.mode.get_id();
|
||||||
|
const theme = api.space.is_dark() ? 'dark' : 'light';
|
||||||
|
return colorSchemeRegistry.getScheme(mode, theme);
|
||||||
|
}
|
||||||
|
|
||||||
const localFilterKey ='kiri-gcode-filters';
|
const localFilterKey ='kiri-gcode-filters';
|
||||||
const localFilters = js2o(local.getItem(localFilterKey)) || [];
|
const localFilters = js2o(local.getItem(localFilterKey)) || [];
|
||||||
|
|
||||||
|
|
@ -322,9 +332,10 @@ function updateExtruderFields(device) {
|
||||||
onclick() {
|
onclick() {
|
||||||
api.selection.for_widgets(w => {
|
api.selection.for_widgets(w => {
|
||||||
w.anno.extruder = i;
|
w.anno.extruder = i;
|
||||||
|
const scheme = getColorScheme();
|
||||||
w.setColor(
|
w.setColor(
|
||||||
api.selection.contains(w) ?
|
api.selection.contains(w) ?
|
||||||
COLOR.selected : COLOR.deselected,
|
scheme.widget.selected : scheme.widget.deselected,
|
||||||
undefined,
|
undefined,
|
||||||
false);
|
false);
|
||||||
});
|
});
|
||||||
|
|
@ -332,7 +343,7 @@ function updateExtruderFields(device) {
|
||||||
}),
|
}),
|
||||||
h.div({
|
h.div({
|
||||||
class: "splat",
|
class: "splat",
|
||||||
style: `background-color: #${COLOR.deselected[i].toString(16)}`
|
style: `background-color: #${getColorScheme().widget.deselected[i].toString(16)}`
|
||||||
})
|
})
|
||||||
])));
|
])));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,19 @@ import { api } from './api.js';
|
||||||
import { client } from './workers.js';
|
import { client } from './workers.js';
|
||||||
import { codec } from '../core/codec.js';
|
import { codec } from '../core/codec.js';
|
||||||
import { space } from '../../moto/space.js';
|
import { space } from '../../moto/space.js';
|
||||||
import { COLOR, PMODES } from '../core/consts.js';
|
import { PMODES } from '../core/consts.js';
|
||||||
|
import { colorSchemeRegistry } from './color/schemes.js';
|
||||||
import { exportFile } from './export.js';
|
import { exportFile } from './export.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current color scheme
|
||||||
|
*/
|
||||||
|
function getColorScheme() {
|
||||||
|
const mode = api.mode.get_id();
|
||||||
|
const theme = api.space.is_dark() ? 'dark' : 'light';
|
||||||
|
return colorSchemeRegistry.getScheme(mode, theme);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks completion state of operations to prevent redundant work.
|
* Tracks completion state of operations to prevent redundant work.
|
||||||
* Properties: slice, preview, export
|
* Properties: slice, preview, export
|
||||||
|
|
@ -96,7 +106,8 @@ function prepareSlices(callback, scale = 1, offset = 0) {
|
||||||
|
|
||||||
let totalProgress;
|
let totalProgress;
|
||||||
|
|
||||||
api.widgets.setOpacity(COLOR.slicing_opacity);
|
const scheme = getColorScheme();
|
||||||
|
api.widgets.setOpacity(scheme.views.SLICE.slicing_opacity);
|
||||||
|
|
||||||
let segtimes = {},
|
let segtimes = {},
|
||||||
segNumber = 0,
|
segNumber = 0,
|
||||||
|
|
@ -110,7 +121,8 @@ function prepareSlices(callback, scale = 1, offset = 0) {
|
||||||
for (let widget of toSlice) {
|
for (let widget of toSlice) {
|
||||||
widget.belt = null;
|
widget.belt = null;
|
||||||
widget.stats.progress = 0;
|
widget.stats.progress = 0;
|
||||||
widget.setColor(COLOR.slicing);
|
const scheme = getColorScheme();
|
||||||
|
widget.setColor(0xffaaaa); // slicing color (from old COLOR.slicing)
|
||||||
let { extruder } = widget.anno;
|
let { extruder } = widget.anno;
|
||||||
if (extruder >= 0) {
|
if (extruder >= 0) {
|
||||||
extruders[extruder] = extruder;
|
extruders[extruder] = extruder;
|
||||||
|
|
@ -277,9 +289,10 @@ function prepareSlices(callback, scale = 1, offset = 0) {
|
||||||
}
|
}
|
||||||
if (scale === 1) {
|
if (scale === 1) {
|
||||||
// clear wireframe
|
// clear wireframe
|
||||||
widget.setWireframe(false, COLOR.wireframe, COLOR.wireframe_opacity);
|
const scheme = getColorScheme();
|
||||||
widget.setOpacity(camOrLaser ? COLOR.cam_sliced_opacity : COLOR.sliced_opacity);
|
widget.setWireframe(false, scheme.wireframe.color, scheme.wireframe.opacity);
|
||||||
widget.setColor(COLOR.deselected);
|
widget.setOpacity(camOrLaser ? scheme.views.SLICE.sliced_opacity ?? 0.2 : scheme.views.PREVIEW?.sliced_opacity ?? 0.0);
|
||||||
|
widget.setColor(scheme.widget.deselected);
|
||||||
api.hide.alert(alert);
|
api.hide.alert(alert);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -365,10 +378,12 @@ function preparePreview(callback, scale = 1, offset = 0) {
|
||||||
event.emit('preview.begin', pMode);
|
event.emit('preview.begin', pMode);
|
||||||
|
|
||||||
if (isCam) {
|
if (isCam) {
|
||||||
api.widgets.setOpacity(isDark ? COLOR.cam_preview_opacity_dark : COLOR.cam_preview_opacity);
|
const scheme = getColorScheme();
|
||||||
api.widgets.setColor(isDark ? COLOR.cam_preview_dark : COLOR.cam_preview);
|
api.widgets.setOpacity(scheme.views.PREVIEW.preview_opacity);
|
||||||
|
api.widgets.setColor(scheme.views.PREVIEW.preview);
|
||||||
} else if (offset === 0) {
|
} else if (offset === 0) {
|
||||||
api.widgets.setOpacity(COLOR.preview_opacity);
|
const scheme = getColorScheme();
|
||||||
|
api.widgets.setOpacity(scheme.views.PREVIEW.preview_opacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mark = Date.now(),
|
let mark = Date.now(),
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
import { $, h } from '../../moto/webui.js';
|
import { $, h } from '../../moto/webui.js';
|
||||||
import { ajax, js2o } from '../core/utils.js';
|
import { ajax, js2o } from '../core/utils.js';
|
||||||
import { api } from './api.js';
|
import { api } from './api.js';
|
||||||
import { COLOR, MODES } from '../core/consts.js';
|
import { MODES } from '../core/consts.js';
|
||||||
|
import { colorSchemeRegistry } from './color/schemes.js';
|
||||||
import { load as file_load } from '../../load/file.js';
|
import { load as file_load } from '../../load/file.js';
|
||||||
import { newBounds } from '../../geo/bounds.js';
|
import { newBounds } from '../../geo/bounds.js';
|
||||||
import { Packer } from './pack.js';
|
import { Packer } from './pack.js';
|
||||||
|
|
@ -13,6 +14,15 @@ import { Widget, newWidget } from './widget.js';
|
||||||
|
|
||||||
const V0 = new THREE.Vector3(0,0,0);
|
const V0 = new THREE.Vector3(0,0,0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current color scheme
|
||||||
|
*/
|
||||||
|
function getColorScheme() {
|
||||||
|
const mode = api.mode.get_id();
|
||||||
|
const theme = api.space.is_dark() ? 'dark' : 'light';
|
||||||
|
return colorSchemeRegistry.getScheme(mode, theme);
|
||||||
|
}
|
||||||
|
|
||||||
/** Bounds update callback */
|
/** Bounds update callback */
|
||||||
let setbounds = undefined;
|
let setbounds = undefined;
|
||||||
|
|
||||||
|
|
@ -154,17 +164,18 @@ function update_size(updateDark = true) {
|
||||||
gridMinor = unitMM ? 5 : 25.4 / 10;
|
gridMinor = unitMM ? 5 : 25.4 / 10;
|
||||||
|
|
||||||
if (updateDark) {
|
if (updateDark) {
|
||||||
|
const scheme = getColorScheme();
|
||||||
if (controller.dark) {
|
if (controller.dark) {
|
||||||
space.platform.set({ light: 0.08 });
|
space.platform.set({ light: 0.08 });
|
||||||
space.platform.setFont({rulerColor:'#888888'});
|
space.platform.setFont({rulerColor:'#888888'});
|
||||||
space.platform.setGrid(gridMajor, gridMinor, 0x666666, 0x333333);
|
space.platform.setGrid(gridMajor, gridMinor, scheme.grid.major, scheme.grid.minor);
|
||||||
space.platform.opacity(0.05);
|
space.platform.opacity(0.05);
|
||||||
space.sky.set({ color: 0, ambient: { intensity: 0.6 } });
|
space.sky.set({ color: 0, ambient: { intensity: 0.6 } });
|
||||||
document.body.classList.add('dark');
|
document.body.classList.add('dark');
|
||||||
} else {
|
} else {
|
||||||
space.platform.set({ light: 0.08 });
|
space.platform.set({ light: 0.08 });
|
||||||
space.platform.setFont({rulerColor:'#333333'});
|
space.platform.setFont({rulerColor:'#333333'});
|
||||||
space.platform.setGrid(gridMajor, gridMinor, 0x999999, 0xcccccc);
|
space.platform.setGrid(gridMajor, gridMinor, scheme.grid.major, scheme.grid.minor);
|
||||||
space.platform.opacity(0.2);
|
space.platform.opacity(0.2);
|
||||||
space.sky.set({ color: 0xffffff, ambient: { intensity: 1.1 } });
|
space.sky.set({ color: 0xffffff, ambient: { intensity: 1.1 } });
|
||||||
document.body.classList.remove('dark');
|
document.body.classList.remove('dark');
|
||||||
|
|
@ -319,16 +330,18 @@ function update_selected() {
|
||||||
let b = $(`sel-ext-${i}`);
|
let b = $(`sel-ext-${i}`);
|
||||||
if (b) b.classList.remove('pop-sel');
|
if (b) b.classList.remove('pop-sel');
|
||||||
}
|
}
|
||||||
|
const scheme = getColorScheme();
|
||||||
selection.for_widgets(w => {
|
selection.for_widgets(w => {
|
||||||
w.setColor(COLOR.selected);
|
w.setColor(scheme.widget.selected);
|
||||||
let ext = api.widgets.annotate(w.id).extruder || 0;
|
let ext = api.widgets.annotate(w.id).extruder || 0;
|
||||||
let b = $(`sel-ext-${ext}`);
|
let b = $(`sel-ext-${ext}`);
|
||||||
if (b) b.classList.add('pop-sel');
|
if (b) b.classList.add('pop-sel');
|
||||||
w.saveState();
|
w.saveState();
|
||||||
}, true);
|
}, true);
|
||||||
} else {
|
} else {
|
||||||
|
const scheme = getColorScheme();
|
||||||
selection.for_widgets(w => {
|
selection.for_widgets(w => {
|
||||||
w.setColor(COLOR.selected);
|
w.setColor(scheme.widget.selected);
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -374,7 +387,8 @@ function select(widget, shift, recurse = true) {
|
||||||
}
|
}
|
||||||
selection.add(widget);
|
selection.add(widget);
|
||||||
event.emit('widget.select', widget);
|
event.emit('widget.select', widget);
|
||||||
widget.setColor(COLOR.selected);
|
const scheme = getColorScheme();
|
||||||
|
widget.setColor(scheme.widget.selected);
|
||||||
$(`ws-${widget.id}`)?.classList.add('selected');
|
$(`ws-${widget.id}`)?.classList.add('selected');
|
||||||
selection.update_info();
|
selection.update_info();
|
||||||
}
|
}
|
||||||
|
|
@ -420,7 +434,8 @@ function deselect(widget, recurse = true) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$(`ws-${widget.id}`)?.classList.remove('selected');
|
$(`ws-${widget.id}`)?.classList.remove('selected');
|
||||||
widget.setColor(COLOR.deselected);
|
const scheme = getColorScheme();
|
||||||
|
widget.setColor(scheme.widget.deselected);
|
||||||
platform.update_selected();
|
platform.update_selected();
|
||||||
selection.update_info();
|
selection.update_info();
|
||||||
space.update();
|
space.update();
|
||||||
|
|
@ -753,8 +768,9 @@ function changed() {
|
||||||
onclick() {
|
onclick() {
|
||||||
let dis = w.meta.disabled = !w.meta.disabled;
|
let dis = w.meta.disabled = !w.meta.disabled;
|
||||||
let sel = api.selection.contains(w);
|
let sel = api.selection.contains(w);
|
||||||
|
const scheme = getColorScheme();
|
||||||
api.uc.setClass($(`w-en-${w.id}`), "disabled", dis);
|
api.uc.setClass($(`w-en-${w.id}`), "disabled", dis);
|
||||||
w.setColor(sel ? COLOR.selected : COLOR.deselected, null, false);
|
w.setColor(sel ? scheme.widget.selected : scheme.widget.deselected, null, false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[ h.i({ class:"fas fa-ban" }) ]
|
[ h.i({ class:"fas fa-ban" }) ]
|
||||||
|
|
|
||||||
215
src/kiri/app/state/visual.js
Normal file
215
src/kiri/app/state/visual.js
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VisualStateManager: Manages 3-layer visual state hierarchy for widgets
|
||||||
|
*
|
||||||
|
* Layer hierarchy (bottom to top):
|
||||||
|
* 1. Base Layer: User persistent preferences (edges, wireframe, opacity)
|
||||||
|
* 2. Mode Layer: Mode+view specific defaults (FDM/ARRANGE vs CAM/PREVIEW)
|
||||||
|
* 3. Operation Layer: Temporary context overrides (paint, selection)
|
||||||
|
*
|
||||||
|
* Context types:
|
||||||
|
* - 'paint': OVERRIDE context (FDM support painting, completely replaces material)
|
||||||
|
* - 'select': TEMPORARY context (selection highlighting)
|
||||||
|
* - null: Normal state changes following hierarchy
|
||||||
|
*/
|
||||||
|
class VisualStateManager {
|
||||||
|
constructor(widget, scheme, api) {
|
||||||
|
this.widget = widget;
|
||||||
|
this.scheme = scheme;
|
||||||
|
this.api = api;
|
||||||
|
|
||||||
|
// State stack (base → mode → operation)
|
||||||
|
this.stack = [];
|
||||||
|
|
||||||
|
// Active operation states
|
||||||
|
this.operations = {}; // { context: state }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load user preferences from localStorage (base layer)
|
||||||
|
*/
|
||||||
|
loadPreferences() {
|
||||||
|
const edges = this.api.local.getBoolean('model.edges');
|
||||||
|
const wireframe = this.api.local.getBoolean('model.wireframe');
|
||||||
|
const wireframeColor = this.api.local.getInt('model.wireframe.color') || 0;
|
||||||
|
const wireframeOpacity = this.api.local.getFloat('model.wireframe.opacity');
|
||||||
|
|
||||||
|
this.baseState = {
|
||||||
|
edges: edges || false,
|
||||||
|
wireframe: wireframe || false,
|
||||||
|
wireframeColor: wireframeColor,
|
||||||
|
wireframeOpacity: wireframeOpacity !== null ? wireframeOpacity : this.scheme.wireframe.opacity,
|
||||||
|
opacity: 1.0
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.baseState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save user preferences to localStorage
|
||||||
|
*/
|
||||||
|
savePreferences() {
|
||||||
|
if (!this.baseState) return;
|
||||||
|
|
||||||
|
this.api.local.set('model.edges', this.baseState.edges);
|
||||||
|
this.api.local.set('model.wireframe', this.baseState.wireframe);
|
||||||
|
if (this.baseState.wireframeColor !== undefined) {
|
||||||
|
this.api.local.set('model.wireframe.color', this.baseState.wireframeColor);
|
||||||
|
}
|
||||||
|
if (this.baseState.wireframeOpacity !== undefined) {
|
||||||
|
this.api.local.set('model.wireframe.opacity', this.baseState.wireframeOpacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply mode-specific defaults (mode layer)
|
||||||
|
* @param {number} mode - Mode ID
|
||||||
|
* @param {number} viewMode - View mode ID (ARRANGE=1, SLICE=2, PREVIEW=3, ANIMATE=4)
|
||||||
|
*/
|
||||||
|
applyModeState(mode, viewMode) {
|
||||||
|
const viewName = this._viewModeToName(viewMode);
|
||||||
|
const viewScheme = this.scheme.views[viewName];
|
||||||
|
|
||||||
|
this.modeState = {
|
||||||
|
mode,
|
||||||
|
viewMode,
|
||||||
|
opacity: viewScheme?.model_opacity ?? 1.0,
|
||||||
|
previewOpacity: viewScheme?.preview_opacity,
|
||||||
|
slicingOpacity: viewScheme?.slicing_opacity,
|
||||||
|
slicedOpacity: viewScheme?.sliced_opacity
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.modeState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push a temporary operation state (operation layer)
|
||||||
|
* @param {string} context - Operation context ('paint', 'select', etc)
|
||||||
|
* @param {object} state - State to push
|
||||||
|
* @param {function} state.restoreCallback - Optional callback to restore state on pop
|
||||||
|
*/
|
||||||
|
pushState(context, state) {
|
||||||
|
if (!context) {
|
||||||
|
console.warn('pushState called without context');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.operations[context] = state;
|
||||||
|
this.stack.push({ layer: 'operation', context, state });
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pop a temporary operation state
|
||||||
|
* @param {string} context - Operation context to pop
|
||||||
|
*/
|
||||||
|
popState(context) {
|
||||||
|
if (!context) {
|
||||||
|
console.warn('popState called without context');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const operation = this.operations[context];
|
||||||
|
if (!operation) {
|
||||||
|
console.warn(`No operation found for context: ${context}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call restore callback if provided
|
||||||
|
if (operation.restoreCallback) {
|
||||||
|
operation.restoreCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from operations and stack
|
||||||
|
delete this.operations[context];
|
||||||
|
this.stack = this.stack.filter(s => s.context !== context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current effective state (topmost in hierarchy)
|
||||||
|
*/
|
||||||
|
getCurrentState() {
|
||||||
|
if (this.stack.length > 0) {
|
||||||
|
return this.stack[this.stack.length - 1].state;
|
||||||
|
}
|
||||||
|
if (this.modeState) {
|
||||||
|
return this.modeState;
|
||||||
|
}
|
||||||
|
return this.baseState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an operation context is currently active
|
||||||
|
* @param {string} context - Operation context to check
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isOperationActive(context) {
|
||||||
|
return context in this.operations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all active operation contexts
|
||||||
|
* @returns {Array<string>}
|
||||||
|
*/
|
||||||
|
getActiveContexts() {
|
||||||
|
return Object.keys(this.operations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all operation states (e.g., on mode switch)
|
||||||
|
*/
|
||||||
|
clearOperations() {
|
||||||
|
// Call restore callbacks for all active operations
|
||||||
|
for (const context in this.operations) {
|
||||||
|
const operation = this.operations[context];
|
||||||
|
if (operation.restoreCallback) {
|
||||||
|
operation.restoreCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.operations = {};
|
||||||
|
this.stack = this.stack.filter(s => s.layer !== 'operation');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get visual state snapshot for undo/redo
|
||||||
|
*/
|
||||||
|
getVisualState() {
|
||||||
|
return {
|
||||||
|
edges: this.widget.outline ? true : false,
|
||||||
|
wires: this.widget.wire ? true : false,
|
||||||
|
opacity: this.widget.getMaterial()?.opacity ?? 1.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore visual state from snapshot
|
||||||
|
* @param {object} state - State snapshot
|
||||||
|
*/
|
||||||
|
setVisualState({ edges, wires, opacity }) {
|
||||||
|
this.widget.cache.vizstate = this.getVisualState();
|
||||||
|
this.widget.setEdges(edges ?? false);
|
||||||
|
this.widget.setWireframe(wires ?? false);
|
||||||
|
if (opacity !== undefined) {
|
||||||
|
this.widget.setOpacity(opacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert view mode ID to name
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_viewModeToName(viewMode) {
|
||||||
|
const views = {
|
||||||
|
1: 'ARRANGE',
|
||||||
|
2: 'SLICE',
|
||||||
|
3: 'PREVIEW',
|
||||||
|
4: 'ANIMATE'
|
||||||
|
};
|
||||||
|
return views[viewMode] || 'ARRANGE';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { VisualStateManager };
|
||||||
|
|
@ -3,12 +3,22 @@
|
||||||
import { api } from './api.js';
|
import { api } from './api.js';
|
||||||
import { base } from '../../geo/base.js';
|
import { base } from '../../geo/base.js';
|
||||||
import { consts } from '../core/consts.js';
|
import { consts } from '../core/consts.js';
|
||||||
|
import { colorSchemeRegistry } from './color/schemes.js';
|
||||||
import { settings } from './conf/manager.js';
|
import { settings } from './conf/manager.js';
|
||||||
import { space } from '../../moto/space.js';
|
import { space } from '../../moto/space.js';
|
||||||
|
|
||||||
import STACKS from './stacks.js';
|
import STACKS from './stacks.js';
|
||||||
|
|
||||||
const { COLOR, VIEWS } = consts;
|
const { VIEWS } = consts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current color scheme
|
||||||
|
*/
|
||||||
|
function getColorScheme() {
|
||||||
|
const mode = api.mode.get_id();
|
||||||
|
const theme = api.space.is_dark() ? 'dark' : 'light';
|
||||||
|
return colorSchemeRegistry.getScheme(mode, theme);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constrain value to min/max bounds.
|
* Constrain value to min/max bounds.
|
||||||
|
|
@ -182,7 +192,8 @@ function hideSlices() {
|
||||||
const wireframe = api.local.getBoolean('model.wireframe');
|
const wireframe = api.local.getBoolean('model.wireframe');
|
||||||
const color = api.local.getInt('model.wireframe.color') || 0;
|
const color = api.local.getInt('model.wireframe.color') || 0;
|
||||||
const saved = api.local.getFloat('model.wireframe.opacity');
|
const saved = api.local.getFloat('model.wireframe.opacity');
|
||||||
const opacity = saved !== null ? saved : COLOR.model_opacity;
|
const scheme = getColorScheme();
|
||||||
|
const opacity = saved !== null ? saved : (scheme.views.ARRANGE?.model_opacity ?? 1.0);
|
||||||
|
|
||||||
if (wireframe) {
|
if (wireframe) {
|
||||||
api.widgets.each(widget => widget.setWireframe(wireframe, color, opacity));
|
api.widgets.each(widget => widget.setWireframe(wireframe, color, opacity));
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
import { Widget as WidgetCore } from '../core/widget.js';
|
import { Widget as WidgetCore } from '../core/widget.js';
|
||||||
import { util } from '../../geo/base.js';
|
import { util } from '../../geo/base.js';
|
||||||
import { avgc } from '../core/utils.js';
|
import { avgc } from '../core/utils.js';
|
||||||
|
import { colorSchemeRegistry } from './color/schemes.js';
|
||||||
|
import { VisualStateManager } from './state/visual.js';
|
||||||
|
|
||||||
const solid_opacity = 1.0;
|
const solid_opacity = 1.0;
|
||||||
|
|
||||||
|
|
@ -14,6 +16,59 @@ function index() { return self.kiri_catalog.index }
|
||||||
* Adds rendering, persistence, and visual state management
|
* Adds rendering, persistence, and visual state management
|
||||||
*/
|
*/
|
||||||
class Widget extends WidgetCore {
|
class Widget extends WidgetCore {
|
||||||
|
constructor(id, group) {
|
||||||
|
super(id, group);
|
||||||
|
// Visual state manager will be initialized when api is available
|
||||||
|
this.visualStateManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize visual state manager (called when api is available)
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_initVisualStateManager() {
|
||||||
|
if (this.visualStateManager || !this.api) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mode = this.api.mode.get_id();
|
||||||
|
const theme = this.api.space.is_dark() ? 'dark' : 'light';
|
||||||
|
const scheme = colorSchemeRegistry.getScheme(mode, theme);
|
||||||
|
|
||||||
|
this.visualStateManager = new VisualStateManager(this, scheme, this.api);
|
||||||
|
this.visualStateManager.loadPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current color scheme
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getScheme() {
|
||||||
|
if (!this.api) return null;
|
||||||
|
const mode = this.api.mode.get_id();
|
||||||
|
const theme = this.api.space.is_dark() ? 'dark' : 'light';
|
||||||
|
return colorSchemeRegistry.getScheme(mode, theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push a temporary visual state (for operations like paint)
|
||||||
|
*/
|
||||||
|
pushVisualState(context, state) {
|
||||||
|
this._initVisualStateManager();
|
||||||
|
if (this.visualStateManager) {
|
||||||
|
return this.visualStateManager.pushState(context, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pop a temporary visual state
|
||||||
|
*/
|
||||||
|
popVisualState(context) {
|
||||||
|
if (this.visualStateManager) {
|
||||||
|
this.visualStateManager.popState(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save widget geometry to catalog
|
* Save widget geometry to catalog
|
||||||
*/
|
*/
|
||||||
|
|
@ -144,26 +199,58 @@ class Widget extends WidgetCore {
|
||||||
// missing api features in engine mode
|
// missing api features in engine mode
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mesh = this.mesh;
|
const mesh = this.mesh;
|
||||||
|
|
||||||
|
// Handle toggle
|
||||||
if (set && set.toggle) {
|
if (set && set.toggle) {
|
||||||
set = this.outline ? false : true;
|
set = this.outline ? false : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove outline (but keep cache)
|
||||||
if (this.outline) {
|
if (this.outline) {
|
||||||
mesh.remove(this.outline);
|
mesh.remove(this.outline);
|
||||||
this.outline = null;
|
this.outline = null;
|
||||||
}
|
}
|
||||||
if (set) {
|
|
||||||
let dark = this.api.space.is_dark();
|
if (!set) {
|
||||||
let cam = this.api.mode.is_cam();
|
return; // Just hide, keep cache
|
||||||
let color = dark ? 0x444444 : 0x888888;
|
}
|
||||||
let angle = this.api.conf.get().controller.edgeangle || 20;
|
|
||||||
let edges = new THREE.EdgesGeometry(mesh.geometry, angle);
|
// Resolve current angle and color
|
||||||
let material = new THREE.LineBasicMaterial({ color });
|
const scheme = this._getScheme();
|
||||||
this.outline = new THREE.LineSegments(edges, material);
|
const angle = scheme?.edges?.angle ?? this.api.conf.get().controller.edgeangle ?? 20;
|
||||||
|
const color = scheme?.edges?.color ?? 0x888888;
|
||||||
|
|
||||||
|
// Initialize cache if needed
|
||||||
|
if (!this.cache.edges) {
|
||||||
|
this.cache.edges = { geometry: null, angle: null, version: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const cache = this.cache.edges;
|
||||||
|
let edgesGeometry;
|
||||||
|
|
||||||
|
// Check if we can reuse cached geometry
|
||||||
|
if (cache.geometry &&
|
||||||
|
cache.angle === angle &&
|
||||||
|
cache.version === this.geomVersion) {
|
||||||
|
// Reuse cached geometry
|
||||||
|
edgesGeometry = cache.geometry;
|
||||||
|
} else {
|
||||||
|
// Regenerate edges
|
||||||
|
edgesGeometry = new THREE.EdgesGeometry(mesh.geometry, angle);
|
||||||
|
|
||||||
|
// Update cache
|
||||||
|
cache.geometry = edgesGeometry;
|
||||||
|
cache.angle = angle;
|
||||||
|
cache.version = this.geomVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create outline with cached/new geometry
|
||||||
|
const material = new THREE.LineBasicMaterial({ color });
|
||||||
|
this.outline = new THREE.LineSegments(edgesGeometry, material);
|
||||||
this.outline.renderOrder = -20;
|
this.outline.renderOrder = -20;
|
||||||
mesh.add(this.outline);
|
mesh.add(this.outline);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set wireframe rendering
|
* Set wireframe rendering
|
||||||
|
|
@ -181,14 +268,16 @@ class Widget extends WidgetCore {
|
||||||
this.wire = null;
|
this.wire = null;
|
||||||
}
|
}
|
||||||
if (set) {
|
if (set) {
|
||||||
let dark = this.api.space.is_dark();
|
const scheme = this._getScheme();
|
||||||
let mat = new THREE.MeshBasicMaterial({
|
const wireColor = scheme?.wireframe?.color ?? 0xaaaaaa;
|
||||||
|
const wireOpacity = scheme?.wireframe?.opacity ?? 0.5;
|
||||||
|
const mat = new THREE.MeshBasicMaterial({
|
||||||
wireframe: true,
|
wireframe: true,
|
||||||
color: dark ? 0xaaaaaa : 0,
|
color: wireColor,
|
||||||
opacity: 0.5,
|
opacity: wireOpacity,
|
||||||
transparent: true
|
transparent: true
|
||||||
})
|
});
|
||||||
let wire = widget.wire = new THREE.Mesh(mesh.geometry.shallowClone(), mat);
|
const wire = widget.wire = new THREE.Mesh(mesh.geometry.shallowClone(), mat);
|
||||||
mesh.add(wire);
|
mesh.add(wire);
|
||||||
}
|
}
|
||||||
if (this.api.view.is_arrange()) {
|
if (this.api.view.is_arrange()) {
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,6 @@
|
||||||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
const COLOR = {
|
// COLOR table has been migrated to src/kiri/app/color/ scheme system
|
||||||
wireframe: 0x444444,
|
|
||||||
wireframe_opacity: 0.25,
|
|
||||||
selected: [ 0xbbff00, 0xbbee00, 0xbbdd00, 0xbb9900 ],
|
|
||||||
deselected: [ 0xffff00, 0xffdd00, 0xffbb00, 0xff9900 ],
|
|
||||||
model_opacity: 1.0,
|
|
||||||
preview_opacity: 0.0,
|
|
||||||
slicing: 0xffaaaa,
|
|
||||||
slicing_opacity: 0.5,
|
|
||||||
sliced_opacity: 0.0,
|
|
||||||
cam_sliced_opacity: 0.2,
|
|
||||||
cam_preview: 0xdddddd,
|
|
||||||
cam_preview_opacity: 0.2,
|
|
||||||
cam_preview_dark: 0x888888,
|
|
||||||
cam_preview_opacity_dark: 0.2,
|
|
||||||
};
|
|
||||||
|
|
||||||
const LISTS = {
|
const LISTS = {
|
||||||
shell: [
|
shell: [
|
||||||
|
|
@ -185,7 +170,6 @@ const LAST = 'kiri-last';
|
||||||
const SEED = 'kiri-seed';
|
const SEED = 'kiri-seed';
|
||||||
|
|
||||||
export let consts = {
|
export let consts = {
|
||||||
COLOR,
|
|
||||||
LAST,
|
LAST,
|
||||||
LISTS,
|
LISTS,
|
||||||
MODES,
|
MODES,
|
||||||
|
|
@ -196,4 +180,4 @@ export let consts = {
|
||||||
beltfact: Math.cos(Math.PI / 4)
|
beltfact: Math.cos(Math.PI / 4)
|
||||||
};
|
};
|
||||||
|
|
||||||
export { COLOR, LAST, LISTS, MODES, VIEWS, PATHS, PMODES, SEED };
|
export { LAST, LISTS, MODES, VIEWS, PATHS, PMODES, SEED };
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,8 @@ class Widget {
|
||||||
this.boundingBoxNeedsUpdate = true
|
this.boundingBoxNeedsUpdate = true
|
||||||
// cache shadow geo
|
// cache shadow geo
|
||||||
this.cache = {};
|
this.cache = {};
|
||||||
|
// geometry version for edge cache invalidation
|
||||||
|
this.geomVersion = 0;
|
||||||
this.stats = {
|
this.stats = {
|
||||||
slice_time: 0,
|
slice_time: 0,
|
||||||
load_time: 0,
|
load_time: 0,
|
||||||
|
|
@ -193,6 +195,7 @@ class Widget {
|
||||||
this.clearShadows();
|
this.clearShadows();
|
||||||
if (reason !== 'axis') {
|
if (reason !== 'axis') {
|
||||||
this.cache.geo = undefined;
|
this.cache.geo = undefined;
|
||||||
|
this.geomVersion++; // Increment version to invalidate edge cache
|
||||||
}
|
}
|
||||||
if (this.mesh && this.mesh.geometry) {
|
if (this.mesh && this.mesh.geometry) {
|
||||||
// this fixes ray intersections after the mesh is modified
|
// this fixes ray intersections after the mesh is modified
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
import { api } from '../../app/api.js';
|
import { api } from '../../app/api.js';
|
||||||
|
import { colorSchemeRegistry } from '../../app/color/schemes.js';
|
||||||
import { env, isDark } from './init-ui.js';
|
import { env, isDark } from './init-ui.js';
|
||||||
import { addbox, delbox, clearboxes } from '../../core/boxes.js';
|
import { addbox, delbox, clearboxes } from '../../core/boxes.js';
|
||||||
import { traceDone } from './cl-trace.js';
|
import { traceDone } from './cl-trace.js';
|
||||||
|
|
@ -225,9 +226,15 @@ export function tabClear() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function boxColor() {
|
function boxColor() {
|
||||||
return isDark() ? 0x00ddff : 0x0000dd;
|
const mode = 3; // CAM mode ID
|
||||||
|
const theme = isDark() ? 'dark' : 'light';
|
||||||
|
const scheme = colorSchemeRegistry.getScheme(mode, theme);
|
||||||
|
return scheme.operations?.tabs?.color ?? (isDark() ? 0x00ddff : 0x0000dd);
|
||||||
}
|
}
|
||||||
|
|
||||||
function boxOpacity() {
|
function boxOpacity() {
|
||||||
return isDark() ? 0.75 : 0.6;
|
const mode = 3; // CAM mode ID
|
||||||
|
const theme = isDark() ? 'dark' : 'light';
|
||||||
|
const scheme = colorSchemeRegistry.getScheme(mode, theme);
|
||||||
|
return scheme.operations?.tabs?.opacity ?? (isDark() ? 0.75 : 0.6);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { newPoint } from '../../../geo/point.js';
|
import { newPoint } from '../../../geo/point.js';
|
||||||
import { $ } from '../../../moto/webui.js';
|
import { $ } from '../../../moto/webui.js';
|
||||||
import { api } from '../../app/api.js';
|
import { api } from '../../app/api.js';
|
||||||
|
import { colorSchemeRegistry } from '../../app/color/schemes.js';
|
||||||
import { conf } from '../../app/conf/defaults.js';
|
import { conf } from '../../app/conf/defaults.js';
|
||||||
import { addPaintOverlayTexture, updatePaintOverlay } from '../../app/paint.js';
|
import { addPaintOverlayTexture, updatePaintOverlay } from '../../app/paint.js';
|
||||||
|
|
||||||
|
|
@ -271,14 +272,29 @@ function supportStart({ remove } = { remove: false }) {
|
||||||
return [ down.widget.mesh ];
|
return [ down.widget.mesh ];
|
||||||
// return api.widgets.meshes();
|
// return api.widgets.meshes();
|
||||||
};
|
};
|
||||||
|
// Get paint color from scheme
|
||||||
|
const mode = api.mode.get_id();
|
||||||
|
const theme = api.space.is_dark() ? 'dark' : 'light';
|
||||||
|
const scheme = colorSchemeRegistry.getScheme(mode, theme);
|
||||||
|
const paintColor = scheme.operations?.paint?.overlay ?? 0x4488ff;
|
||||||
|
|
||||||
api.widgets.each(w => {
|
api.widgets.each(w => {
|
||||||
|
// Use pushVisualState to track the paint operation
|
||||||
|
w.pushVisualState('paint', {
|
||||||
|
material: w.mesh.material[0],
|
||||||
|
restoreCallback: () => {
|
||||||
|
w.mesh.material[0] = w.cache.mat;
|
||||||
|
delete w.cache.mat;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let mat = w.cache.mat = w.mesh.material[0];
|
let mat = w.cache.mat = w.mesh.material[0];
|
||||||
mat = w.mesh.material[0] = mat.clone();
|
mat = w.mesh.material[0] = mat.clone();
|
||||||
mat.needsUpdate = true;
|
mat.needsUpdate = true;
|
||||||
if (!w.anno.paint) {
|
if (!w.anno.paint) {
|
||||||
w.anno.paint = [];
|
w.anno.paint = [];
|
||||||
}
|
}
|
||||||
addPaintOverlayTexture(mat, w.anno.paint, new THREE.Color(0x4488ff));
|
addPaintOverlayTexture(mat, w.anno.paint, new THREE.Color(paintColor));
|
||||||
})
|
})
|
||||||
api.space.update();
|
api.space.update();
|
||||||
}
|
}
|
||||||
|
|
@ -321,8 +337,8 @@ function supportDone() {
|
||||||
api.feature.on_mouse_drag = undefined;
|
api.feature.on_mouse_drag = undefined;
|
||||||
api.hide.alert(alert);
|
api.hide.alert(alert);
|
||||||
api.widgets.each(w => {
|
api.widgets.each(w => {
|
||||||
w.mesh.material[0] = w.cache.mat;
|
// Use popVisualState to restore original material
|
||||||
delete w.cache.mat;
|
w.popVisualState('paint');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue