@nice2dev/ui-3d 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +103 -0
- package/dist/cjs/core/editorAsk.js +48 -0
- package/dist/cjs/core/i18n.js +7 -3
- package/dist/cjs/dance/DanceScoreEngine.js +7 -0
- package/dist/cjs/dance/PoseDetector.js +4 -2
- package/dist/cjs/material/NiceMaterialEditor.js +7 -3
- package/dist/cjs/material/materialEditorTypes.js +1 -1
- package/dist/cjs/material/materialNodeDefinitions.js +14 -11
- package/dist/cjs/model/ModelEditor.js +3 -2
- package/dist/cjs/model/ModelEditorLeftPanel.js +10 -4
- package/dist/cjs/model/ModelEditorMenuBar.js +7 -5
- package/dist/cjs/model/ModelEditorRightPanel.js +18 -16
- package/dist/cjs/model/ModelEditorSubComponents.js +8 -6
- package/dist/cjs/model/ModelEditorTimeline.js +6 -4
- package/dist/cjs/model/ModelEditorToolbar.js +6 -4
- package/dist/cjs/model/ModelEditorViewport.js +16 -6
- package/dist/cjs/model/ModelViewer.js +6 -4
- package/dist/cjs/model/NiceArmatureEditor.js +6 -4
- package/dist/cjs/model/NiceMorphTargetEditor.js +5 -3
- package/dist/cjs/model/NiceOctree.js +3 -1
- package/dist/cjs/model/NicePhysicsSimulation.js +6 -4
- package/dist/cjs/model/NiceProceduralGeometry.js +5 -3
- package/dist/cjs/model/NiceTerrainEditor.js +8 -6
- package/dist/cjs/model/NiceWeightPainter.js +4 -2
- package/dist/cjs/model/NiceXRPreview.js +4 -2
- package/dist/cjs/model/useModelEditor.js +22 -15
- package/dist/cjs/nice2dev-ui-3d.css +1 -1
- package/dist/cjs/particle/NiceParticleEditor.js +14 -9
- package/dist/cjs/rendering/NiceCascadedShadows.js +3 -1
- package/dist/cjs/rendering/NiceRenderExport.js +9 -3
- package/dist/cjs/rendering/NiceSSAO.js +3 -1
- package/dist/cjs/rendering/NiceSSR.js +3 -1
- package/dist/cjs/rendering/NiceWebGPURenderer.js +4 -2
- package/dist/cjs/uv/NiceUVEditor.js +16 -12
- package/dist/cjs/uv/uvEditorTypes.js +1 -0
- package/dist/esm/core/editorAsk.js +45 -0
- package/dist/esm/core/i18n.js +8 -4
- package/dist/esm/dance/DanceScoreEngine.js +7 -0
- package/dist/esm/dance/PoseDetector.js +4 -2
- package/dist/esm/material/NiceMaterialEditor.js +7 -3
- package/dist/esm/material/materialEditorTypes.js +1 -1
- package/dist/esm/material/materialNodeDefinitions.js +14 -11
- package/dist/esm/model/ModelEditor.js +3 -2
- package/dist/esm/model/ModelEditorLeftPanel.js +10 -4
- package/dist/esm/model/ModelEditorMenuBar.js +7 -5
- package/dist/esm/model/ModelEditorRightPanel.js +18 -16
- package/dist/esm/model/ModelEditorSubComponents.js +8 -6
- package/dist/esm/model/ModelEditorTimeline.js +6 -4
- package/dist/esm/model/ModelEditorToolbar.js +6 -4
- package/dist/esm/model/ModelEditorViewport.js +16 -6
- package/dist/esm/model/ModelViewer.js +6 -4
- package/dist/esm/model/NiceArmatureEditor.js +6 -4
- package/dist/esm/model/NiceMorphTargetEditor.js +5 -3
- package/dist/esm/model/NiceOctree.js +3 -1
- package/dist/esm/model/NicePhysicsSimulation.js +6 -4
- package/dist/esm/model/NiceProceduralGeometry.js +5 -3
- package/dist/esm/model/NiceTerrainEditor.js +8 -6
- package/dist/esm/model/NiceWeightPainter.js +4 -2
- package/dist/esm/model/NiceXRPreview.js +4 -2
- package/dist/esm/model/useModelEditor.js +22 -15
- package/dist/esm/nice2dev-ui-3d.css +1 -1
- package/dist/esm/particle/NiceParticleEditor.js +14 -9
- package/dist/esm/rendering/NiceCascadedShadows.js +3 -1
- package/dist/esm/rendering/NiceRenderExport.js +9 -3
- package/dist/esm/rendering/NiceSSAO.js +3 -1
- package/dist/esm/rendering/NiceSSR.js +3 -1
- package/dist/esm/rendering/NiceWebGPURenderer.js +4 -2
- package/dist/esm/uv/NiceUVEditor.js +16 -12
- package/dist/esm/uv/uvEditorTypes.js +1 -0
- package/dist/types/core/editorAsk.d.ts +26 -0
- package/dist/types/core/i18n.d.ts +1 -17
- package/dist/types/dance/DanceScoreEngine.d.ts +0 -6
- package/dist/types/model/useModelEditor.d.ts +3 -0
- package/package.json +3 -3
|
@@ -4,6 +4,7 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var THREE = require('three');
|
|
7
|
+
var i18n = require('../core/i18n.js');
|
|
7
8
|
var threeCompat = require('../core/threeCompat.js');
|
|
8
9
|
|
|
9
10
|
function _interopNamespaceDefault(e) {
|
|
@@ -230,14 +231,15 @@ const TONE_MAPPINGS = [
|
|
|
230
231
|
];
|
|
231
232
|
/* ── Component ── */
|
|
232
233
|
const NiceWebGPURenderer = ({ canvas, scene, camera, width, height, onRendererReady, className = '', }) => {
|
|
234
|
+
const { t } = i18n.useNiceTranslation();
|
|
233
235
|
const { isWebGPU, capabilities, isReady, toneMapping, setToneMapping, toneMappingExposure, setToneMappingExposure, outputColorSpace, setOutputColorSpace, pixelRatio, setPixelRatio, } = useWebGPURenderer(canvas, scene, camera, width, height, onRendererReady);
|
|
234
|
-
return (jsxRuntime.jsxs("div", { className: `nice-webgpu-renderer ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "monitor", size: 14 }), " Renderer
|
|
236
|
+
return (jsxRuntime.jsxs("div", { className: `nice-webgpu-renderer ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "monitor", size: 14 }), " ", t('webGPURenderer.renderer', 'Renderer')] }), jsxRuntime.jsx("span", { style: {
|
|
235
237
|
fontSize: 10,
|
|
236
238
|
padding: '2px 6px',
|
|
237
239
|
borderRadius: 4,
|
|
238
240
|
background: isWebGPU ? 'var(--nice-success, #22c55e)' : 'var(--nice-warning, #f59e0b)',
|
|
239
241
|
color: 'var(--nice-color-white, #fff)',
|
|
240
|
-
}, children: isWebGPU ? 'WebGPU' : 'WebGL' })] }), !isReady && jsxRuntime.jsx("div", { style: { opacity: 0.6, fontSize: 11 }, children: "Initializing\u2026" }), capabilities && (jsxRuntime.jsxs("div", { style: { fontSize: 10, opacity: 0.7, lineHeight: 1.5 }, children: ["GPU: ", capabilities.adapterName, jsxRuntime.jsx("br", {}), "Vendor: ", capabilities.vendor, jsxRuntime.jsx("br", {}), "Max Texture: ", capabilities.maxTextureDimension2D, "px", capabilities.features.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("br", {}), "Features: ", capabilities.features.length] }))] })), jsxRuntime.jsxs("label", { children: ["Tone Mapping:", jsxRuntime.jsx("select", { value: toneMapping, onChange: (e) => setToneMapping(parseInt(e.target.value, 10)), style: { width: '100%', marginTop: 2 }, children: TONE_MAPPINGS.map((tm) => (jsxRuntime.jsx("option", { value: tm.value, children: tm.label }, tm.value))) })] }), jsxRuntime.jsxs("label", { children: ["Exposure: ", toneMappingExposure.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 3, step: 0.05, value: toneMappingExposure, onChange: (e) => setToneMappingExposure(parseFloat(e.target.value)), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Output Color Space:", jsxRuntime.jsxs("select", { value: outputColorSpace, onChange: (e) => setOutputColorSpace(e.target.value), style: { width: '100%', marginTop: 2 }, children: [jsxRuntime.jsx("option", { value: THREE__namespace.SRGBColorSpace, children: "sRGB" }), jsxRuntime.jsx("option", { value: THREE__namespace.LinearSRGBColorSpace, children:
|
|
242
|
+
}, children: isWebGPU ? 'WebGPU' : 'WebGL' })] }), !isReady && jsxRuntime.jsx("div", { style: { opacity: 0.6, fontSize: 11 }, children: "Initializing\u2026" }), capabilities && (jsxRuntime.jsxs("div", { style: { fontSize: 10, opacity: 0.7, lineHeight: 1.5 }, children: ["GPU: ", capabilities.adapterName, jsxRuntime.jsx("br", {}), "Vendor: ", capabilities.vendor, jsxRuntime.jsx("br", {}), "Max Texture: ", capabilities.maxTextureDimension2D, "px", capabilities.features.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("br", {}), "Features: ", capabilities.features.length] }))] })), jsxRuntime.jsxs("label", { children: ["Tone Mapping:", jsxRuntime.jsx("select", { value: toneMapping, onChange: (e) => setToneMapping(parseInt(e.target.value, 10)), style: { width: '100%', marginTop: 2 }, children: TONE_MAPPINGS.map((tm) => (jsxRuntime.jsx("option", { value: tm.value, children: tm.label }, tm.value))) })] }), jsxRuntime.jsxs("label", { children: ["Exposure: ", toneMappingExposure.toFixed(2), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 3, step: 0.05, value: toneMappingExposure, onChange: (e) => setToneMappingExposure(parseFloat(e.target.value)), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Output Color Space:", jsxRuntime.jsxs("select", { value: outputColorSpace, onChange: (e) => setOutputColorSpace(e.target.value), style: { width: '100%', marginTop: 2 }, children: [jsxRuntime.jsx("option", { value: THREE__namespace.SRGBColorSpace, children: "sRGB" }), jsxRuntime.jsx("option", { value: THREE__namespace.LinearSRGBColorSpace, children: t('webGPURenderer.linearSrgb', 'Linear sRGB') })] })] }), jsxRuntime.jsxs("label", { children: ["Pixel Ratio: ", pixelRatio.toFixed(1), jsxRuntime.jsx("input", { type: "range", min: 0.5, max: 3, step: 0.5, value: pixelRatio, onChange: (e) => setPixelRatio(parseFloat(e.target.value)), style: { width: '100%' } })] })] }));
|
|
241
243
|
};
|
|
242
244
|
|
|
243
245
|
exports.NiceWebGPURenderer = NiceWebGPURenderer;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
5
|
var React = require('react');
|
|
6
|
+
var i18n = require('../core/i18n.js');
|
|
6
7
|
var UVEditor_module = require('./UVEditor.module.css.js');
|
|
7
8
|
var uvEditorTypes = require('./uvEditorTypes.js');
|
|
8
9
|
var uvEditorUtils = require('./uvEditorUtils.js');
|
|
@@ -22,6 +23,7 @@ const UV_EDITOR_TOUR = [
|
|
|
22
23
|
Component
|
|
23
24
|
═══════════════════════════════════════════ */
|
|
24
25
|
const NiceUVEditor = ({ geometry, texture, channel = 'uv', onUVChange, onSelectionChange, width = '100%', height = 600, showToolbar = true, showSidebar = true, className, readOnly = false, tutorial, }) => {
|
|
26
|
+
const { t } = i18n.useNiceTranslation();
|
|
25
27
|
const canvasRef = React.useRef(null);
|
|
26
28
|
const containerRef = React.useRef(null);
|
|
27
29
|
// State
|
|
@@ -124,12 +126,14 @@ const NiceUVEditor = ({ geometry, texture, channel = 'uv', onUVChange, onSelecti
|
|
|
124
126
|
ctx.closePath();
|
|
125
127
|
ctx.stroke();
|
|
126
128
|
}
|
|
127
|
-
// Draw faces
|
|
129
|
+
// Draw faces — the two fills resolved once per frame, not once per face
|
|
130
|
+
const faceSelectedFill = ui.resolveCssColor('var(--nice-warning-tint-30, rgba(255, 170, 0, 0.3))', ctx.canvas);
|
|
131
|
+
const faceFill = ui.resolveCssColor('var(--nice-overlay-10, rgba(100, 100, 100, 0.1))', ctx.canvas);
|
|
128
132
|
for (const face of meshData.faces.values()) {
|
|
129
133
|
const verts = face.vertices.map((id) => meshData.vertices.get(id));
|
|
130
134
|
const screenVerts = verts.map((v) => toScreen(v.position));
|
|
131
135
|
// Fill
|
|
132
|
-
ctx.fillStyle = face.selected ?
|
|
136
|
+
ctx.fillStyle = face.selected ? faceSelectedFill : faceFill;
|
|
133
137
|
ctx.beginPath();
|
|
134
138
|
ctx.moveTo(screenVerts[0].x, screenVerts[0].y);
|
|
135
139
|
screenVerts.slice(1).forEach((sv) => ctx.lineTo(sv.x, sv.y));
|
|
@@ -530,33 +534,33 @@ const NiceUVEditor = ({ geometry, texture, channel = 'uv', onUVChange, onSelecti
|
|
|
530
534
|
setMeshData(newData);
|
|
531
535
|
onUVChange === null || onUVChange === void 0 ? void 0 : onUVChange(geometry);
|
|
532
536
|
}, [meshData, geometry, state.activeChannel, readOnly, pushUndo, onUVChange]);
|
|
533
|
-
const toolbarSlot = showToolbar ? (jsxRuntime.jsxs("div", { className: UVEditor_module.toolbar, children: [jsxRuntime.jsx("div", { className: UVEditor_module.toolGroup, children: uvEditorTypes.UV_TOOLS.slice(0, 5).map((tool) => (jsxRuntime.jsx("button", { className: `${UVEditor_module.toolBtn} ${state.activeTool === tool ? UVEditor_module.active : ''}`, onClick: () => setState((prev) => ({ ...prev, activeTool: tool })), title: tool, disabled: readOnly, children: tool[0].toUpperCase() }, tool))) }), jsxRuntime.jsx("div", { className: UVEditor_module.toolGroup, children: uvEditorTypes.UV_SELECTION_MODES.map((mode) => (jsxRuntime.jsx("button", { className: `${UVEditor_module.toolBtn} ${state.selectionMode === mode ? UVEditor_module.active : ''}`, onClick: () => setState((prev) => ({ ...prev, selectionMode: mode })), title: mode, children: mode[0].toUpperCase() }, mode))) }), jsxRuntime.jsxs("div", { className: UVEditor_module.toolGroup, children: [jsxRuntime.jsx("button", { className: UVEditor_module.toolBtn, onClick: handlePack, disabled: readOnly || !meshData, title:
|
|
537
|
+
const toolbarSlot = showToolbar ? (jsxRuntime.jsxs("div", { className: UVEditor_module.toolbar, children: [jsxRuntime.jsx("div", { className: UVEditor_module.toolGroup, children: uvEditorTypes.UV_TOOLS.slice(0, 5).map((tool) => (jsxRuntime.jsx("button", { className: `${UVEditor_module.toolBtn} ${state.activeTool === tool ? UVEditor_module.active : ''}`, onClick: () => setState((prev) => ({ ...prev, activeTool: tool })), title: tool, disabled: readOnly, children: tool[0].toUpperCase() }, tool))) }), jsxRuntime.jsx("div", { className: UVEditor_module.toolGroup, children: uvEditorTypes.UV_SELECTION_MODES.map((mode) => (jsxRuntime.jsx("button", { className: `${UVEditor_module.toolBtn} ${state.selectionMode === mode ? UVEditor_module.active : ''}`, onClick: () => setState((prev) => ({ ...prev, selectionMode: mode })), title: mode, children: mode[0].toUpperCase() }, mode))) }), jsxRuntime.jsxs("div", { className: UVEditor_module.toolGroup, children: [jsxRuntime.jsx("button", { className: UVEditor_module.toolBtn, onClick: handlePack, disabled: readOnly || !meshData, title: t('uvEditor.packIslandsP', 'Pack Islands (P)'), children: t('uvEditor.pack', 'Pack') }), jsxRuntime.jsx("button", { className: UVEditor_module.toolBtn, onClick: handleRelax, disabled: readOnly || !meshData, title: t('uvEditor.relaxUvs', 'Relax UVs'), children: t('uvEditor.relax', 'Relax') })] }), jsxRuntime.jsx("div", { className: UVEditor_module.toolGroup, children: jsxRuntime.jsxs("select", { className: UVEditor_module.select, onChange: (e) => handleProject(e.target.value), disabled: readOnly || !meshData, defaultValue: "", children: [jsxRuntime.jsx("option", { value: "", disabled: true, children: t('uvEditor.project', 'Project...') }), uvEditorTypes.PROJECTION_TYPES.map((type) => (jsxRuntime.jsx("option", { value: type, children: type }, type)))] }) }), jsxRuntime.jsx("div", { className: UVEditor_module.toolGroup, children: jsxRuntime.jsx("select", { className: UVEditor_module.select, value: state.activeChannel, onChange: (e) => setState((prev) => ({
|
|
534
538
|
...prev,
|
|
535
539
|
activeChannel: e.target.value,
|
|
536
|
-
})), children: uvEditorTypes.UV_CHANNELS.map((ch) => (jsxRuntime.jsx("option", { value: ch, children: ch.toUpperCase() }, ch))) }) }), jsxRuntime.jsxs("div", { className: UVEditor_module.toolGroup, children: [jsxRuntime.jsx("button", { className: UVEditor_module.toolBtn, onClick: undo, disabled: undoStack.length === 0, title:
|
|
537
|
-
const sidebarSlot = showSidebar && meshData ? (jsxRuntime.jsxs("div", { className: UVEditor_module.sidebar, children: [jsxRuntime.jsxs("div", { className: UVEditor_module.panel, children: [jsxRuntime.jsx("h4", { className: UVEditor_module.panelTitle, children:
|
|
540
|
+
})), children: uvEditorTypes.UV_CHANNELS.map((ch) => (jsxRuntime.jsx("option", { value: ch, children: ch.toUpperCase() }, ch))) }) }), jsxRuntime.jsxs("div", { className: UVEditor_module.toolGroup, children: [jsxRuntime.jsx("button", { className: UVEditor_module.toolBtn, onClick: undo, disabled: undoStack.length === 0, title: t('uvEditor.undoCtrlZ', 'Undo (Ctrl+Z)'), "aria-label": t('uvEditor.undo', 'Undo'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "arrow-left", size: 16 }) }), jsxRuntime.jsx("button", { className: UVEditor_module.toolBtn, onClick: redo, disabled: redoStack.length === 0, title: t('uvEditor.redoCtrlY', 'Redo (Ctrl+Y)'), "aria-label": t('uvEditor.redo', 'Redo'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "arrow-right", size: 16 }) })] })] })) : undefined;
|
|
541
|
+
const sidebarSlot = showSidebar && meshData ? (jsxRuntime.jsxs("div", { className: UVEditor_module.sidebar, children: [jsxRuntime.jsxs("div", { className: UVEditor_module.panel, children: [jsxRuntime.jsx("h4", { className: UVEditor_module.panelTitle, children: t('uvEditor.display', 'Display') }), jsxRuntime.jsxs("label", { className: UVEditor_module.checkbox, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: state.display.showGrid, onChange: (e) => setState((prev) => ({
|
|
538
542
|
...prev,
|
|
539
543
|
display: { ...prev.display, showGrid: e.target.checked },
|
|
540
|
-
})) }),
|
|
544
|
+
})) }), t('uvEditor.showGrid', 'Show Grid')] }), jsxRuntime.jsxs("label", { className: UVEditor_module.checkbox, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: state.display.showTexture, onChange: (e) => setState((prev) => ({
|
|
541
545
|
...prev,
|
|
542
546
|
display: { ...prev.display, showTexture: e.target.checked },
|
|
543
|
-
})) }),
|
|
547
|
+
})) }), t('uvEditor.showTexture', 'Show Texture')] }), jsxRuntime.jsxs("label", { className: UVEditor_module.checkbox, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: state.display.showSeams, onChange: (e) => setState((prev) => ({
|
|
544
548
|
...prev,
|
|
545
549
|
display: { ...prev.display, showSeams: e.target.checked },
|
|
546
|
-
})) }),
|
|
550
|
+
})) }), t('uvEditor.showSeams', 'Show Seams')] }), jsxRuntime.jsxs("label", { className: UVEditor_module.checkbox, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: state.display.showDistortion, onChange: (e) => setState((prev) => ({
|
|
547
551
|
...prev,
|
|
548
552
|
display: { ...prev.display, showDistortion: e.target.checked },
|
|
549
|
-
})) }),
|
|
553
|
+
})) }), t('uvEditor.showDistortion', 'Show Distortion')] }), jsxRuntime.jsxs("label", { className: UVEditor_module.checkbox, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: state.display.showIslandBounds, onChange: (e) => setState((prev) => ({
|
|
550
554
|
...prev,
|
|
551
555
|
display: { ...prev.display, showIslandBounds: e.target.checked },
|
|
552
|
-
})) }),
|
|
556
|
+
})) }), t('uvEditor.showIslandBounds', 'Show Island Bounds')] })] }), jsxRuntime.jsxs("div", { className: UVEditor_module.panel, children: [jsxRuntime.jsx("h4", { className: UVEditor_module.panelTitle, children: t('uvEditor.statistics', 'Statistics') }), jsxRuntime.jsxs("div", { className: UVEditor_module.stat, children: [jsxRuntime.jsx("span", { children: "Vertices:" }), jsxRuntime.jsx("span", { children: meshData.vertices.size })] }), jsxRuntime.jsxs("div", { className: UVEditor_module.stat, children: [jsxRuntime.jsx("span", { children: "Faces:" }), jsxRuntime.jsx("span", { children: meshData.faces.size })] }), jsxRuntime.jsxs("div", { className: UVEditor_module.stat, children: [jsxRuntime.jsx("span", { children: "Islands:" }), jsxRuntime.jsx("span", { children: meshData.islands.size })] }), jsxRuntime.jsxs("div", { className: UVEditor_module.stat, children: [jsxRuntime.jsx("span", { children: "Selected:" }), jsxRuntime.jsx("span", { children: uvEditorUtils.getSelectedVertices(meshData).length })] })] }), jsxRuntime.jsxs("div", { className: UVEditor_module.panel, children: [jsxRuntime.jsx("h4", { className: UVEditor_module.panelTitle, children: t('uvEditor.snapping', 'Snapping') }), jsxRuntime.jsxs("label", { className: UVEditor_module.checkbox, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: state.snapping, onChange: (e) => setState((prev) => ({
|
|
553
557
|
...prev,
|
|
554
558
|
snapping: e.target.checked,
|
|
555
|
-
})) }),
|
|
559
|
+
})) }), t('uvEditor.enableSnapping', 'Enable Snapping')] }), jsxRuntime.jsxs("label", { className: UVEditor_module.rangeLabel, children: ["Snap Size: ", state.snapSize.toFixed(4), jsxRuntime.jsx("input", { type: "range", min: "0.001", max: "0.25", step: "0.001", value: state.snapSize, onChange: (e) => setState((prev) => ({
|
|
556
560
|
...prev,
|
|
557
561
|
snapSize: parseFloat(e.target.value),
|
|
558
562
|
})) })] })] })] })) : undefined;
|
|
559
|
-
return (jsxRuntime.jsxs("div", { style: { position: 'relative', width, height }, children: [jsxRuntime.jsx("span", { style: { position: 'absolute', top: 6, right: 6, zIndex: 30 }, children: jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, UV_EDITOR_TOUR) }) }), jsxRuntime.jsx(ui.NiceAppShell, { className: `nice-uv-editor ${UVEditor_module.uvEditor} ${className !== null && className !== void 0 ? className : ''}`, flush: true, toolbar: toolbarSlot, rightSidebar: sidebarSlot, statusBar: jsxRuntime.jsxs("div", { className: UVEditor_module.statusBar, children: [jsxRuntime.jsxs("span", { children: ["Tool: ", state.activeTool] }), jsxRuntime.jsxs("span", { children: ["Mode: ", state.selectionMode] }), jsxRuntime.jsxs("span", { children: ["Zoom: ", (state.zoom * 100).toFixed(0), "%"] }), jsxRuntime.jsxs("span", { children: ["Channel: ", state.activeChannel] })] }), children: jsxRuntime.jsxs("div", { ref: containerRef, className: UVEditor_module.viewport, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onMouseLeave: handleMouseUp, onWheel: handleWheel, children: [jsxRuntime.jsx("canvas", { ref: canvasRef, className: UVEditor_module.canvas }), !meshData && jsxRuntime.jsx("div", { className: UVEditor_module.noGeometry, children:
|
|
563
|
+
return (jsxRuntime.jsxs("div", { style: { position: 'relative', width, height }, children: [jsxRuntime.jsx("span", { style: { position: 'absolute', top: 6, right: 6, zIndex: 30 }, children: jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, UV_EDITOR_TOUR) }) }), jsxRuntime.jsx(ui.NiceAppShell, { className: `nice-uv-editor ${UVEditor_module.uvEditor} ${className !== null && className !== void 0 ? className : ''}`, flush: true, toolbar: toolbarSlot, rightSidebar: sidebarSlot, statusBar: jsxRuntime.jsxs("div", { className: UVEditor_module.statusBar, children: [jsxRuntime.jsxs("span", { children: ["Tool: ", state.activeTool] }), jsxRuntime.jsxs("span", { children: ["Mode: ", state.selectionMode] }), jsxRuntime.jsxs("span", { children: ["Zoom: ", (state.zoom * 100).toFixed(0), "%"] }), jsxRuntime.jsxs("span", { children: ["Channel: ", state.activeChannel] })] }), children: jsxRuntime.jsxs("div", { ref: containerRef, className: UVEditor_module.viewport, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onMouseLeave: handleMouseUp, onWheel: handleWheel, children: [jsxRuntime.jsx("canvas", { ref: canvasRef, className: UVEditor_module.canvas }), !meshData && (jsxRuntime.jsx("div", { className: UVEditor_module.noGeometry, children: t('uvEditor.noGeometrySelected', 'No geometry selected') }))] }) })] }));
|
|
560
564
|
};
|
|
561
565
|
|
|
562
566
|
exports.NiceUVEditor = NiceUVEditor;
|
|
@@ -50,6 +50,7 @@ const DEFAULT_UV_DISPLAY = {
|
|
|
50
50
|
showDistortion: false,
|
|
51
51
|
distortionGradient: { low: 'var(--nice-success, #00ff00)', high: 'var(--nice-danger, #ff0000)' },
|
|
52
52
|
showSeams: true,
|
|
53
|
+
// audit-allow: literal-color — the seam colour setting's default
|
|
53
54
|
seamColor: '#ff6600',
|
|
54
55
|
highlightSelected: true,
|
|
55
56
|
selectionColor: 'var(--nice-warning, #ffaa00)',
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { NiceConfirmDialog, NicePromptDialog } from '@nice2dev/ui';
|
|
3
|
+
import { useState, useRef, useCallback } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The library's dialogs in place of `window.prompt` / `window.confirm` for the editor hooks,
|
|
7
|
+
* which hold state but render nothing: `ask` resolves with the answer (`null` when cancelled)
|
|
8
|
+
* and `pendingAsk` is the open question, for the editor component to pass to
|
|
9
|
+
* {@link EditorAskDialog}. One question at a time: a new one settles the open one as cancelled.
|
|
10
|
+
*/
|
|
11
|
+
function useEditorAsk() {
|
|
12
|
+
const [pendingAsk, setPendingAsk] = useState(null);
|
|
13
|
+
const pendingRef = useRef(null);
|
|
14
|
+
const ask = useCallback((kind, text, defaultValue) => new Promise((resolve) => {
|
|
15
|
+
var _a;
|
|
16
|
+
(_a = pendingRef.current) === null || _a === void 0 ? void 0 : _a.answer(null);
|
|
17
|
+
const next = {
|
|
18
|
+
kind,
|
|
19
|
+
text,
|
|
20
|
+
defaultValue,
|
|
21
|
+
answer: (value) => {
|
|
22
|
+
if (pendingRef.current === next) {
|
|
23
|
+
pendingRef.current = null;
|
|
24
|
+
setPendingAsk(null);
|
|
25
|
+
}
|
|
26
|
+
resolve(value);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
pendingRef.current = next;
|
|
30
|
+
setPendingAsk(next);
|
|
31
|
+
}), []);
|
|
32
|
+
return { pendingAsk, ask };
|
|
33
|
+
}
|
|
34
|
+
/** Shows the question {@link useEditorAsk} is waiting on, if any. */
|
|
35
|
+
const EditorAskDialog = ({ ask }) => {
|
|
36
|
+
if (!ask) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
if (ask.kind === 'confirm') {
|
|
40
|
+
return (jsx(NiceConfirmDialog, { visible: true, message: ask.text, onAccept: () => ask.answer(''), onReject: () => ask.answer(null) }));
|
|
41
|
+
}
|
|
42
|
+
return (jsx(NicePromptDialog, { open: true, title: ask.text, defaultValue: ask.defaultValue, onSubmit: (value) => ask.answer(value), onCancel: () => ask.answer(null) }));
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export { EditorAskDialog, useEditorAsk };
|
package/dist/esm/core/i18n.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import {
|
|
2
|
+
import { useNiceTranslation as useNiceTranslation$1 } from '@nice2dev/ui';
|
|
3
|
+
import { useContext, useMemo, createContext } from 'react';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
const NiceI18nContext = createContext(
|
|
5
|
+
/** `null` outside this package's provider: the global translator applies. */
|
|
6
|
+
const NiceI18nContext = /* @__PURE__ */ createContext(null);
|
|
7
|
+
/** Overrides the app's global translator for the 3D controls below it. */
|
|
6
8
|
const NiceI18nProvider = ({ t, children }) => (jsx(NiceI18nContext.Provider, { value: t, children: children }));
|
|
7
9
|
function useNiceTranslation() {
|
|
8
|
-
const
|
|
10
|
+
const own = useContext(NiceI18nContext);
|
|
11
|
+
const { t: global } = useNiceTranslation$1();
|
|
12
|
+
const t = useMemo(() => own !== null && own !== void 0 ? own : ((key, defaultValue) => global(key, defaultValue)), [own, global]);
|
|
9
13
|
return { t };
|
|
10
14
|
}
|
|
11
15
|
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { useRef, useState, useCallback } from 'react';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* DanceScoreEngine — Compares detected poses against reference choreography
|
|
5
|
+
*
|
|
6
|
+
* Calculates real-time dance accuracy by comparing camera-detected Pose3DFrames
|
|
7
|
+
* against a reference sequence (from choreography DSL or recorded poses).
|
|
8
|
+
*/
|
|
3
9
|
// ── Default config ──────────────────────────────────────────────
|
|
4
10
|
const DEFAULT_CONFIG = {
|
|
5
11
|
keypointWeights: {},
|
|
@@ -190,6 +196,7 @@ class DanceScoreEngine {
|
|
|
190
196
|
return 'F';
|
|
191
197
|
}
|
|
192
198
|
}
|
|
199
|
+
// ── React hook ──────────────────────────────────────────────────
|
|
193
200
|
function useDanceScore(config) {
|
|
194
201
|
const engineRef = useRef(new DanceScoreEngine(config));
|
|
195
202
|
const [lastScore, setLastScore] = useState(null);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { resolveCssColor } from '@nice2dev/ui';
|
|
3
3
|
import { forwardRef, useRef, useImperativeHandle, useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { useNiceTranslation } from '../core/i18n.js';
|
|
4
5
|
|
|
5
6
|
// ── Standard keypoint definitions (COCO 17-keypoint model) ──────
|
|
6
7
|
const COCO_KEYPOINTS = [
|
|
@@ -147,7 +148,8 @@ function usePoseDetection(config = {}) {
|
|
|
147
148
|
return { start, stop, isDetecting, getLatestFrame: () => latestFrameRef.current };
|
|
148
149
|
}
|
|
149
150
|
// ── Component: PoseDetector ─────────────────────────────────────
|
|
150
|
-
const NicePoseDetector = forwardRef(function NicePoseDetector(props, ref) {
|
|
151
|
+
const NicePoseDetector = /* @__PURE__ */ forwardRef(function NicePoseDetector(props, ref) {
|
|
152
|
+
const { t } = useNiceTranslation();
|
|
151
153
|
const { onFrame, onStart, onStop, onError, className, style, width = 640, height = 480, mirror = true, showSkeleton = true, showConfidence = false, ...config } = props;
|
|
152
154
|
const videoRef = useRef(null);
|
|
153
155
|
const canvasRef = useRef(null);
|
|
@@ -283,7 +285,7 @@ const NicePoseDetector = forwardRef(function NicePoseDetector(props, ref) {
|
|
|
283
285
|
cursor: 'pointer',
|
|
284
286
|
fontSize: 14,
|
|
285
287
|
fontWeight: 600,
|
|
286
|
-
}, children:
|
|
288
|
+
}, children: t('poseDetector.startCamera', 'Start Camera') }))] }));
|
|
287
289
|
});
|
|
288
290
|
|
|
289
291
|
export { NicePoseDetector, usePoseDetection };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { NiceTutorialButton, resolveTutorial, NiceAppShell, NiceIcon, resolveCssColor } from '@nice2dev/ui';
|
|
3
3
|
import { useRef, useState, useMemo, useEffect, useCallback, useReducer } from 'react';
|
|
4
|
+
import { useNiceTranslation } from '../core/i18n.js';
|
|
4
5
|
import styles from './MaterialEditor.module.css.js';
|
|
5
6
|
import { MAX_ZOOM, MIN_ZOOM, NODE_WIDTH, NODE_HEADER_HEIGHT, PORT_HEIGHT, PORT_TYPE_COLORS, NODE_CATEGORY_COLORS, NODE_CATEGORY_NAMES, DEFAULT_GRID_SIZE } from './materialEditorTypes.js';
|
|
6
7
|
import { compileShader, screenToGraph, importGraphFromJSON, exportGraphToJSON, updateNodeInGraph, calculateZoomToFit, cloneNode, generateNodeId, removeNodesFromGraph, removeConnectionFromGraph, getNodeDefinition, canConnect, createConnection, addConnectionToGraph, moveNodesInGraph, createNode, addNodeToGraph, createEmptyGraph } from './materialEditorUtils.js';
|
|
@@ -365,6 +366,7 @@ function useMaterialEditor(initialGraph) {
|
|
|
365
366
|
return { state, dispatch };
|
|
366
367
|
}
|
|
367
368
|
function NodePalette({ searchQuery, expandedCategories, onSearch, onToggleCategory, onAddNode, onDragStart, }) {
|
|
369
|
+
const { t } = useNiceTranslation();
|
|
368
370
|
const filteredNodes = searchQuery ? searchNodes(searchQuery) : MATERIAL_NODE_DEFINITIONS;
|
|
369
371
|
const categories = useMemo(() => {
|
|
370
372
|
const cats = new Map();
|
|
@@ -376,7 +378,7 @@ function NodePalette({ searchQuery, expandedCategories, onSearch, onToggleCatego
|
|
|
376
378
|
}
|
|
377
379
|
return cats;
|
|
378
380
|
}, [filteredNodes]);
|
|
379
|
-
return (jsxs("div", { className: styles.palette, children: [jsx("div", { className: styles.paletteSearch, children: jsx("input", { type: "text", placeholder:
|
|
381
|
+
return (jsxs("div", { className: styles.palette, children: [jsx("div", { className: styles.paletteSearch, children: jsx("input", { type: "text", placeholder: t('materialEditor.searchNodes', 'Search nodes...'), value: searchQuery, onChange: (e) => onSearch(e.target.value), className: styles.searchInput }) }), jsx("div", { className: styles.paletteCategories, children: Array.from(categories.entries()).map(([category, nodes]) => (jsxs("div", { className: styles.paletteCategory, children: [jsxs("button", { className: styles.categoryHeader, onClick: () => onToggleCategory(category), style: { borderLeftColor: NODE_CATEGORY_COLORS[category] }, children: [jsx("span", { className: styles.categoryIcon, children: jsx(NiceIcon, { name: expandedCategories.includes(category) ? 'chevron-down' : 'chevron-right', size: 14 }) }), jsx("span", { children: NODE_CATEGORY_NAMES[category] }), jsx("span", { className: styles.categoryCount, children: nodes.length })] }), expandedCategories.includes(category) && (jsx("div", { className: styles.categoryNodes, children: nodes.map((nodeDef) => (jsxs("div", { className: styles.paletteNode, draggable: true, onDragStart: (e) => onDragStart(nodeDef.type, e), onDoubleClick: () => onAddNode(nodeDef.type), title: nodeDef.description, children: [jsx("span", { className: styles.nodeColor, style: {
|
|
380
382
|
backgroundColor: nodeDef.color || NODE_CATEGORY_COLORS[nodeDef.category],
|
|
381
383
|
} }), jsx("span", { className: styles.nodeName, children: nodeDef.name })] }, nodeDef.type))) }))] }, category))) })] }));
|
|
382
384
|
}
|
|
@@ -452,9 +454,11 @@ function GraphNode({ node, definition, selected, viewTransform, connections, onS
|
|
|
452
454
|
}) })] })), node.comment && jsx("div", { className: styles.nodeComment, children: node.comment })] }));
|
|
453
455
|
}
|
|
454
456
|
function Toolbar({ canUndo, canRedo, canPaste, hasSelection, autoCompile, compiling, showGrid, snapToGrid, onUndo, onRedo, onCopy, onCut, onPaste, onDuplicate, onDelete, onSelectAll, onZoomToFit, onToggleGrid, onToggleSnap, onToggleAutoCompile, onCompile, onNew, onExport, onImport, }) {
|
|
455
|
-
|
|
457
|
+
const { t } = useNiceTranslation();
|
|
458
|
+
return (jsxs("div", { className: styles.toolbar, children: [jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onNew, title: t('materialEditor.newMaterialCtrlN', 'New Material (Ctrl+N)'), "aria-label": t('materialEditor.newMaterial', 'New Material'), children: jsx(NiceIcon, { name: "file", size: 16 }) }), jsx("button", { onClick: onImport, title: t('materialEditor.importCtrlO', 'Import (Ctrl+O)'), "aria-label": t('materialEditor.import', 'Import'), children: jsx(NiceIcon, { name: "folder", size: 16 }) }), jsx("button", { onClick: onExport, title: t('materialEditor.exportCtrlS', 'Export (Ctrl+S)'), "aria-label": t('materialEditor.export', 'Export'), children: jsx(NiceIcon, { name: "save", size: 16 }) })] }), jsx("div", { className: styles.toolbarSeparator }), jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onUndo, disabled: !canUndo, title: t('materialEditor.undoCtrlZ', 'Undo (Ctrl+Z)'), "aria-label": t('materialEditor.undo', 'Undo'), children: jsx(NiceIcon, { name: "arrow-left", size: 16 }) }), jsx("button", { onClick: onRedo, disabled: !canRedo, title: t('materialEditor.redoCtrlY', 'Redo (Ctrl+Y)'), "aria-label": t('materialEditor.redo', 'Redo'), children: jsx(NiceIcon, { name: "arrow-right", size: 16 }) })] }), jsx("div", { className: styles.toolbarSeparator }), jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onCut, disabled: !hasSelection, title: t('materialEditor.cutCtrlX', 'Cut (Ctrl+X)'), "aria-label": t('materialEditor.cut', 'Cut'), children: jsx(NiceIcon, { name: "scissors", size: 16 }) }), jsx("button", { onClick: onCopy, disabled: !hasSelection, title: t('materialEditor.copyCtrlC', 'Copy (Ctrl+C)'), "aria-label": t('materialEditor.copy', 'Copy'), children: jsx(NiceIcon, { name: "clipboard", size: 16 }) }), jsx("button", { onClick: onPaste, disabled: !canPaste, title: t('materialEditor.pasteCtrlV', 'Paste (Ctrl+V)'), "aria-label": t('materialEditor.paste', 'Paste'), children: jsx(NiceIcon, { name: "download", size: 16 }) }), jsx("button", { onClick: onDuplicate, disabled: !hasSelection, title: t('materialEditor.duplicateCtrlD', 'Duplicate (Ctrl+D)'), "aria-label": t('materialEditor.duplicate', 'Duplicate'), children: jsx(NiceIcon, { name: "refresh", size: 16 }) }), jsx("button", { onClick: onDelete, disabled: !hasSelection, title: t('materialEditor.deleteDel', 'Delete (Del)'), "aria-label": t('materialEditor.delete', 'Delete'), children: jsx(NiceIcon, { name: "trash", size: 16 }) })] }), jsx("div", { className: styles.toolbarSeparator }), jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onSelectAll, title: t('materialEditor.selectAllCtrlA', 'Select All (Ctrl+A)'), "aria-label": t('materialEditor.selectAll', 'Select All'), children: jsx(NiceIcon, { name: "square", size: 16 }) }), jsx("button", { onClick: onZoomToFit, title: t('materialEditor.zoomToFitF', 'Zoom to Fit (F)'), "aria-label": t('materialEditor.zoomToFit', 'Zoom to Fit'), children: jsx(NiceIcon, { name: "search", size: 16 }) }), jsx("button", { onClick: onToggleGrid, className: showGrid ? styles.active : '', title: t('materialEditor.toggleGridG', 'Toggle Grid (G)'), "aria-label": t('materialEditor.toggleGrid', 'Toggle Grid'), children: jsx(NiceIcon, { name: "hash", size: 16 }) }), jsx("button", { onClick: onToggleSnap, className: snapToGrid ? styles.active : '', title: t('materialEditor.toggleSnapS', 'Toggle Snap (S)'), "aria-label": t('materialEditor.toggleSnap', 'Toggle Snap'), children: jsx(NiceIcon, { name: "target", size: 16 }) })] }), jsx("div", { className: styles.toolbarSeparator }), jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onToggleAutoCompile, className: autoCompile ? styles.active : '', title: t('materialEditor.autoCompile', 'Auto Compile'), "aria-label": t('materialEditor.autoCompile', 'Auto Compile'), children: jsx(NiceIcon, { name: "zap", size: 16 }) }), jsx("button", { onClick: onCompile, disabled: compiling, title: t('materialEditor.compileF5', 'Compile (F5)'), "aria-label": t('materialEditor.compile', 'Compile'), children: jsx(NiceIcon, { name: compiling ? 'clock' : 'play', size: 16 }) })] })] }));
|
|
456
459
|
}
|
|
457
460
|
function PreviewPanel({ shader, mode, compiling, onModeChange }) {
|
|
461
|
+
const { t } = useNiceTranslation();
|
|
458
462
|
const canvasRef = useRef(null);
|
|
459
463
|
const previewModes = ['sphere', 'cube', 'plane', 'cylinder', 'torus'];
|
|
460
464
|
// Simple preview rendering (in real implementation would use Three.js)
|
|
@@ -484,7 +488,7 @@ function PreviewPanel({ shader, mode, compiling, onModeChange }) {
|
|
|
484
488
|
ctx.arc(100, 100, 80, 0, Math.PI * 2);
|
|
485
489
|
ctx.fill();
|
|
486
490
|
}, [shader, mode]);
|
|
487
|
-
return (jsxs("div", { className: styles.previewPanel, children: [jsxs("div", { className: styles.previewHeader, children: [jsx("span", { children:
|
|
491
|
+
return (jsxs("div", { className: styles.previewPanel, children: [jsxs("div", { className: styles.previewHeader, children: [jsx("span", { children: t('materialEditor.preview', 'Preview') }), jsx("select", { value: mode, onChange: (e) => onModeChange(e.target.value), className: styles.previewModeSelect, children: previewModes.map((m) => (jsx("option", { value: m, children: m.charAt(0).toUpperCase() + m.slice(1) }, m))) })] }), jsxs("div", { className: styles.previewCanvas, children: [compiling && (jsx("div", { className: styles.previewCompiling, children: t('materialEditor.compiling', 'Compiling...') })), jsx("canvas", { ref: canvasRef, width: 200, height: 200 })] }), (shader === null || shader === void 0 ? void 0 : shader.errors.length) ? (jsx("div", { className: styles.previewErrors, children: shader.errors.slice(0, 3).map((err, i) => (jsx("div", { className: styles.previewError, children: err.message }, i))) })) : null] }));
|
|
488
492
|
}
|
|
489
493
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
490
494
|
Main Component
|
|
@@ -42,7 +42,7 @@ const PORT_TYPE_COLORS = {
|
|
|
42
42
|
vec4: 'var(--nice-warning, #ffc107)',
|
|
43
43
|
color: 'var(--nice-danger, #ff5722)',
|
|
44
44
|
texture2d: 'var(--nice-info, #00bcd4)',
|
|
45
|
-
textureCube: '#03A9F4',
|
|
45
|
+
textureCube: 'var(--nice-color-primary, #03A9F4)',
|
|
46
46
|
sampler: 'var(--nice-accent, #3f51b5)',
|
|
47
47
|
matrix3: 'var(--nice-accent, #673ab7)',
|
|
48
48
|
matrix4: 'var(--nice-accent, #9c27b0)',
|
|
@@ -1450,17 +1450,20 @@ const logicNodes = [
|
|
|
1450
1450
|
EXPORTS
|
|
1451
1451
|
═══════════════════════════════════════════════════════════════════════════ */
|
|
1452
1452
|
/** All built-in node definitions */
|
|
1453
|
-
const MATERIAL_NODE_DEFINITIONS =
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1453
|
+
const MATERIAL_NODE_DEFINITIONS =
|
|
1454
|
+
/* @__PURE__ */ (function () {
|
|
1455
|
+
return [
|
|
1456
|
+
...inputNodes,
|
|
1457
|
+
...outputNodes,
|
|
1458
|
+
...mathNodes,
|
|
1459
|
+
...vectorNodes,
|
|
1460
|
+
...colorNodes,
|
|
1461
|
+
...textureNodes,
|
|
1462
|
+
...proceduralNodes,
|
|
1463
|
+
...utilityNodes,
|
|
1464
|
+
...logicNodes,
|
|
1465
|
+
];
|
|
1466
|
+
})();
|
|
1464
1467
|
/** Node definitions map by type */
|
|
1465
1468
|
const MATERIAL_NODE_MAP = new Map(MATERIAL_NODE_DEFINITIONS.map((def) => [def.type, def]));
|
|
1466
1469
|
/** Get nodes by category */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { NiceAppShell } from '@nice2dev/ui';
|
|
3
|
+
import { EditorAskDialog } from '../core/editorAsk.js';
|
|
3
4
|
import styles from './ModelEditor.module.css.js';
|
|
4
5
|
import ModelEditorLeftPanel from './ModelEditorLeftPanel.js';
|
|
5
6
|
import ModelEditorMenuBar from './ModelEditorMenuBar.js';
|
|
@@ -10,7 +11,7 @@ import { SUPPORTED_EXTENSIONS } from './modelEditorTypes.js';
|
|
|
10
11
|
import ModelEditorViewport from './ModelEditorViewport.js';
|
|
11
12
|
import { useModelEditor } from './useModelEditor.js';
|
|
12
13
|
|
|
13
|
-
const ACCEPT = SUPPORTED_EXTENSIONS.map((e) => `.${e}`).join(',');
|
|
14
|
+
const ACCEPT = /* @__PURE__ */ SUPPORTED_EXTENSIONS.map((e) => `.${e}`).join(',');
|
|
14
15
|
const NiceModelEditor = ({ className, onSaveToLibrary, onAIPoseVideo, }) => {
|
|
15
16
|
const api = useModelEditor({ onSaveToLibrary, onAIPoseVideo });
|
|
16
17
|
return (jsxs(Fragment, { children: [jsx("input", { ref: api.fileInputRef, type: "file", accept: ACCEPT, style: { display: 'none' }, onChange: (e) => {
|
|
@@ -27,7 +28,7 @@ const NiceModelEditor = ({ className, onSaveToLibrary, onAIPoseVideo, }) => {
|
|
|
27
28
|
api.mergeAnimationFBX(f);
|
|
28
29
|
}
|
|
29
30
|
e.target.value = '';
|
|
30
|
-
} }), jsx("input", { ref: api.videoInputRef, type: "file", accept: "video/*", style: { display: 'none' }, onChange: api.handleAIVideo }), jsx(NiceAppShell, { className: `${styles.root} ${className !== null && className !== void 0 ? className : ''}`, flush: true, menubar: jsx(ModelEditorMenuBar, { api: api, hasOnSaveToLibrary: !!onSaveToLibrary }), sidebar: jsxs("div", { style: { display: 'flex', minHeight: 0 }, children: [jsx(ModelEditorLeftPanel, { api: api }), jsx(ModelEditorToolbar, { api: api })] }), rightSidebar: jsx(ModelEditorRightPanel, { api: api }), bottomPanel: jsx(ModelEditorTimeline, { api: api }), statusBar: jsxs("div", { className: styles.statusBar, children: [jsx("span", { children: api.statusText }), jsxs("span", { style: { marginLeft: 'auto' }, children: [api.polyCount.toLocaleString(), " triangles \u00B7 ", api.meshCount, " meshes \u00B7 ", api.boneCount, ' ', "bones \u00B7 ", api.animations.length, " anim(s)"] })] }), children: jsx(ModelEditorViewport, { api: api }) })] }));
|
|
31
|
+
} }), jsx("input", { ref: api.videoInputRef, type: "file", accept: "video/*", style: { display: 'none' }, onChange: api.handleAIVideo }), jsx(EditorAskDialog, { ask: api.pendingAsk }), jsx(NiceAppShell, { className: `${styles.root} ${className !== null && className !== void 0 ? className : ''}`, flush: true, menubar: jsx(ModelEditorMenuBar, { api: api, hasOnSaveToLibrary: !!onSaveToLibrary }), sidebar: jsxs("div", { style: { display: 'flex', minHeight: 0 }, children: [jsx(ModelEditorLeftPanel, { api: api }), jsx(ModelEditorToolbar, { api: api })] }), rightSidebar: jsx(ModelEditorRightPanel, { api: api }), bottomPanel: jsx(ModelEditorTimeline, { api: api }), statusBar: jsxs("div", { className: styles.statusBar, children: [jsx("span", { children: api.statusText }), jsxs("span", { style: { marginLeft: 'auto' }, children: [api.polyCount.toLocaleString(), " triangles \u00B7 ", api.meshCount, " meshes \u00B7 ", api.boneCount, ' ', "bones \u00B7 ", api.animations.length, " anim(s)"] })] }), children: jsx(ModelEditorViewport, { api: api }) })] }));
|
|
31
32
|
};
|
|
32
33
|
|
|
33
34
|
export { NiceModelEditor };
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import { NiceIcon, NiceButton, NiceTextInput } from '@nice2dev/ui';
|
|
3
3
|
import React from 'react';
|
|
4
|
+
import { useNiceTranslation } from '../core/i18n.js';
|
|
4
5
|
import styles from './ModelEditor.module.css.js';
|
|
5
6
|
|
|
6
|
-
const ModelEditorLeftPanel =
|
|
7
|
+
const ModelEditorLeftPanel = /* @__PURE__ */ Object.assign(
|
|
8
|
+
/* @__PURE__ */ React.memo(({ api }) => {
|
|
9
|
+
const { t } = useNiceTranslation();
|
|
7
10
|
const { sceneTree, selectedId, outlinerSearch, setOutlinerSearch, selectObject, rebuildTree, toggleNodeExpanded, toggleNodeVisibility, } = api;
|
|
8
11
|
const renderTreeNode = (node, depth) => {
|
|
9
12
|
// Outliner search filter
|
|
@@ -37,8 +40,11 @@ const ModelEditorLeftPanel = React.memo(({ api }) => {
|
|
|
37
40
|
toggleNodeVisibility(node);
|
|
38
41
|
}, children: jsx(NiceIcon, { name: node.visible ? 'eye' : 'eye-off', size: 14 }) })] }), node.expanded && node.children.map((c) => renderTreeNode(c, depth + 1))] }, node.id));
|
|
39
42
|
};
|
|
40
|
-
return (jsx("div", { className: styles.leftPanel, children: jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "clipboard", size: 14 }), "
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
return (jsx("div", { className: styles.leftPanel, children: jsxs("div", { className: styles.panelSection, children: [jsxs("div", { className: styles.panelTitle, children: [jsx(NiceIcon, { name: "clipboard", size: 14 }), " ", t('modelEditorLeftPanel.outliner', 'Outliner'), jsx(NiceButton, { variant: "ghost", size: "sm", onClick: rebuildTree, title: t('modelEditorLeftPanel.refresh', 'Refresh'), "aria-label": t('modelEditorLeftPanel.refreshOutliner', 'Refresh outliner'), children: jsx(NiceIcon, { name: "refresh", size: 14 }) })] }), jsx("div", { className: styles.outlinerSearch, children: jsx(NiceTextInput, { className: styles.propInput, placeholder: t('modelEditorLeftPanel.filter', 'Filter...'), value: outlinerSearch, onChange: (val) => setOutlinerSearch(val), style: { width: '100%', marginBottom: 4 } }) }), jsxs("div", { className: styles.panelContent, style: { overflowY: 'auto', flex: 1 }, children: [sceneTree.length === 0 && (jsx("div", { style: {
|
|
44
|
+
color: 'var(--nice-text-secondary, #666)',
|
|
45
|
+
fontSize: 10,
|
|
46
|
+
padding: '8px 0',
|
|
47
|
+
}, children: t('modelEditorLeftPanel.dropA3dFileTo', 'Drop a 3D file to begin') })), sceneTree.map((n) => renderTreeNode(n, 0))] })] }) }));
|
|
48
|
+
}), { displayName: 'ModelEditorLeftPanel' });
|
|
43
49
|
|
|
44
50
|
export { ModelEditorLeftPanel as default };
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import { NiceSelect, NiceButton, NiceIcon, NiceCheckbox, NiceColorPicker } from '@nice2dev/ui';
|
|
3
3
|
import React from 'react';
|
|
4
|
+
import { useNiceTranslation } from '../core/i18n.js';
|
|
4
5
|
import styles from './ModelEditor.module.css.js';
|
|
5
6
|
|
|
6
|
-
const ModelEditorMenuBar =
|
|
7
|
+
const ModelEditorMenuBar = /* @__PURE__ */ Object.assign(
|
|
8
|
+
/* @__PURE__ */ React.memo(({ api, hasOnSaveToLibrary }) => {
|
|
9
|
+
const { t } = useNiceTranslation();
|
|
7
10
|
const { editorMode, setEditorMode, fileInputRef, mergeInputRef, addMenuOpen, setAddMenuOpen, addPrimitive, addSceneLight, addCameraObject, addEmpty, selectedNode, duplicateSelected, deleteSelected, exportGLTF, exportOBJ, exportSTL, exportPLY, exportUSDZ, saveToLibrary, clearScene, showGrid, setShowGrid, showAxes, setShowAxes, wireframe, setWireframe, showSkeleton, setShowSkeleton, showLightHelpers, setShowLightHelpers, bgColor, setBgColor, } = api;
|
|
8
11
|
return (jsxs("div", { className: styles.menuBar, children: [jsx(NiceSelect, { className: styles.modeSelect, value: editorMode, onChange: (val) => setEditorMode(val), options: [
|
|
9
12
|
{ value: 'object', label: 'Object Mode' },
|
|
10
13
|
{ value: 'edit', label: 'Edit Mode' },
|
|
11
14
|
{ value: 'pose', label: 'Pose Mode' },
|
|
12
|
-
] }), jsx("div", { className: styles.menuSep }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: [jsx(NiceIcon, { name: "folder", size: 14 }), " Open
|
|
15
|
+
] }), jsx("div", { className: styles.menuSep }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: [jsx(NiceIcon, { name: "folder", size: 14 }), " ", t('modelEditorMenuBar.open', 'Open')] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => { var _a; return (_a = mergeInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, title: t('modelEditorMenuBar.mergeAnimationsFromFbxGltf', 'Merge animations from FBX/GLTF onto current model'), children: [jsx(NiceIcon, { name: "plus", size: 14 }), " ", t('modelEditorMenuBar.mergeAnim', 'Merge Anim')] }), jsx("div", { className: styles.menuSep }), jsxs("div", { className: styles.menuDropdownWrap, children: [jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => setAddMenuOpen(addMenuOpen ? null : 'mesh'), children: [jsx(NiceIcon, { name: "plus", size: 14 }), " Add ", jsx(NiceIcon, { name: "chevron-down", size: 14 })] }), addMenuOpen && (jsxs("div", { className: styles.dropdownMenu, children: [jsx("div", { className: styles.dropdownTitle, children: t('modelEditorMenuBar.mesh', 'Mesh') }), [
|
|
13
16
|
'cube',
|
|
14
17
|
'sphere',
|
|
15
18
|
'cylinder',
|
|
@@ -23,8 +26,7 @@ const ModelEditorMenuBar = React.memo(({ api, hasOnSaveToLibrary }) => {
|
|
|
23
26
|
'octahedron',
|
|
24
27
|
'tetrahedron',
|
|
25
28
|
'torusKnot',
|
|
26
|
-
].map((t) => (jsx("div", { className: styles.dropdownItem, onClick: () => addPrimitive(t), children: t.charAt(0).toUpperCase() + t.slice(1) }, t))), jsx("div", { className: styles.dropdownSep }), jsx("div", { className: styles.dropdownTitle, children:
|
|
27
|
-
});
|
|
28
|
-
ModelEditorMenuBar.displayName = 'ModelEditorMenuBar';
|
|
29
|
+
].map((t) => (jsx("div", { className: styles.dropdownItem, onClick: () => addPrimitive(t), children: t.charAt(0).toUpperCase() + t.slice(1) }, t))), jsx("div", { className: styles.dropdownSep }), jsx("div", { className: styles.dropdownTitle, children: t('modelEditorMenuBar.light', 'Light') }), ['point', 'spot', 'directional', 'hemisphere'].map((t) => (jsxs("div", { className: styles.dropdownItem, onClick: () => addSceneLight(t), children: [jsx(NiceIcon, { name: "lightbulb", size: 14 }), " ", t.charAt(0).toUpperCase() + t.slice(1)] }, t))), jsx("div", { className: styles.dropdownSep }), jsx("div", { className: styles.dropdownTitle, children: t('modelEditorMenuBar.other', 'Other') }), jsxs("div", { className: styles.dropdownItem, onClick: addCameraObject, children: [jsx(NiceIcon, { name: "camera", size: 14 }), " ", t('modelEditorMenuBar.camera', 'Camera')] }), jsxs("div", { className: styles.dropdownItem, onClick: () => addEmpty('arrows'), children: [jsx(NiceIcon, { name: "move", size: 14 }), " Empty (Arrows)"] })] }))] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: duplicateSelected, disabled: !selectedNode, title: t('modelEditorMenuBar.duplicateSelectedShiftD', 'Duplicate selected (Shift+D)'), children: [jsx(NiceIcon, { name: "clipboard", size: 14 }), " ", t('modelEditorMenuBar.duplicate', 'Duplicate')] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: deleteSelected, disabled: !selectedNode, title: t('modelEditorMenuBar.deleteSelectedDelete', 'Delete selected (Delete)'), children: [jsx(NiceIcon, { name: "close", size: 14 }), " ", t('modelEditorMenuBar.delete', 'Delete')] }), jsx("div", { className: styles.menuSep }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => exportGLTF(true), children: [jsx(NiceIcon, { name: "save", size: 14 }), " ", t('modelEditorMenuBar.exportGlb', 'Export GLB')] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => exportGLTF(false), children: [jsx(NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportGltf', 'Export GLTF')] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: exportOBJ, children: [jsx(NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportObj', 'Export OBJ')] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => exportSTL(true), children: [jsx(NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportStl', 'Export STL')] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: () => exportPLY(true), children: [jsx(NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportPly', 'Export PLY')] }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: exportUSDZ, children: [jsx(NiceIcon, { name: "file", size: 14 }), " ", t('modelEditorMenuBar.exportUsdz', 'Export USDZ')] }), hasOnSaveToLibrary && (jsxs(Fragment, { children: [jsx("div", { className: styles.menuSep }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: saveToLibrary, children: [jsx(NiceIcon, { name: "box", size: 14 }), ' ', t('modelEditorMenuBar.saveToLibrary', 'Save to Library')] })] })), jsx("div", { className: styles.menuSep }), jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", onClick: clearScene, children: [jsx(NiceIcon, { name: "trash", size: 14 }), " ", t('modelEditorMenuBar.clear', 'Clear')] }), jsx("div", { className: styles.menuSep }), jsx(NiceCheckbox, { checked: showGrid, onChange: () => setShowGrid((v) => !v), label: t('modelEditorMenuBar.grid', 'Grid') }), jsx(NiceCheckbox, { checked: showAxes, onChange: () => setShowAxes((v) => !v), label: t('modelEditorMenuBar.axes', 'Axes') }), jsx(NiceCheckbox, { checked: wireframe, onChange: () => setWireframe((v) => !v), label: t('modelEditorMenuBar.wire', 'Wire') }), jsx(NiceCheckbox, { checked: showSkeleton, onChange: () => setShowSkeleton((v) => !v), label: t('modelEditorMenuBar.skel', 'Skel') }), jsx(NiceCheckbox, { checked: showLightHelpers, onChange: () => setShowLightHelpers((v) => !v), label: t('modelEditorMenuBar.helpers', 'Helpers') }), jsxs("div", { className: styles.menuRight, children: [jsx("label", { className: styles.menuLabel, children: "BG" }), jsx(NiceColorPicker, { value: bgColor, onChange: (c) => setBgColor(c) })] })] }));
|
|
30
|
+
}), { displayName: 'ModelEditorMenuBar' });
|
|
29
31
|
|
|
30
32
|
export { ModelEditorMenuBar as default };
|