Добро пожаловать, Гость
Вам необходимо зарегистрироваться перед тем, как пытататься написать сообщение.

Имя пользователя
  

Пароль
  





Поиск по сообществам

(Расширенный поиск)

Статистика сообществ
» Участники: 72
» Последний участник: artmebel69.ru-Abisa
» Темы сообществ: 38
» Сообщения сообществ: 64

Полная статистика

Пользователи в сети
Сейчас 2 пользователей в сети.
» 0 Участник(-ов) | 2 Гость(-ей)

Последние темы
Как получить документацию...
Сообщество: Динамические приложения в WINNUM Platform
Последнее сообщение: Lamantur
09-24-2025, 09:28 AM
» Ответы: 1
» Просмотры: 271
Панель с кнопками для 3D ...
Сообщество: Динамические приложения в WINNUM Platform
Последнее сообщение: nponyatov
09-19-2025, 09:32 AM
» Ответы: 0
» Просмотры: 345
Панель с кнопками для 3D ...
Сообщество: Динамические приложения в WINNUM Platform
Последнее сообщение: nponyatov
09-19-2025, 08:31 AM
» Ответы: 0
» Просмотры: 286
Добавление библиотеки в п...
Сообщество: Динамические приложения в WINNUM Platform
Последнее сообщение: Lamantur
08-22-2025, 07:18 AM
» Ответы: 0
» Просмотры: 282
WinnumAjaxAuthHandler - П...
Сообщество: Динамические приложения в WINNUM Platform
Последнее сообщение: Lamantur
05-27-2025, 09:15 AM
» Ответы: 0
» Просмотры: 343
Эффективность работы опер...
Сообщество: Динамические приложения в WINNUM Platform
Последнее сообщение: Сергей Борисов
04-28-2025, 01:18 PM
» Ответы: 1
» Просмотры: 441
Переход на страницу
Сообщество: Динамические приложения в WINNUM Platform
Последнее сообщение: Lamantur
03-18-2025, 09:32 AM
» Ответы: 0
» Просмотры: 316
Убрать все всплывающие по...
Сообщество: Динамические приложения в WINNUM Platform
Последнее сообщение: Lamantur
03-18-2025, 08:42 AM
» Ответы: 0
» Просмотры: 317
График работы оборудовани...
Сообщество: Динамические приложения в WINNUM Platform
Последнее сообщение: Lamantur
02-20-2025, 01:05 PM
» Ответы: 0
» Просмотры: 454
Выбор объекта
Сообщество: Динамические приложения в WINNUM Platform
Последнее сообщение: Lamantur
01-21-2025, 12:23 PM
» Ответы: 0
» Просмотры: 342

 
  Как получить документацию по Winnum sdk
Автор: artiist172 - 09-24-2025, 08:52 AM - Сообщество: Динамические приложения в WINNUM Platform - Ответы (1)

Здравствуйте!  Недавно начал работать с продуктом Winnum и столкнулся с трудностью: не могу найти документацию к Winnum SDK. На официальном сайте разработчика мне найти ее не удалось.
Может быть, кто-то из сообщества сталкивался с подобной задачей и знает, где можно скачать руководство по его использованию? Буду очень благодарен за любую помощь или наводку.

Распечатать этот элемент

Photo Панель с кнопками для 3D v2
Автор: nponyatov - 09-19-2025, 09:32 AM - Сообщество: Динамические приложения в WINNUM Platform - Нет ответов

В приложении Цифровой двойник довольно часто используются кнопки. Я решил создать вторую версию панели с кнопками для 3D в стиле ЧПУ-станка. Здесь будет представлен расширенный функционал.
В данном посте я хотел бы рассказать о том как добавить их себе в 3D-сцену и настроить. Они смогут помочь стилизовать сцену и улучшить восприятие.

1. Скачать файл ViewButtons (во вложении) и импортировать себе в сцену (Файл - Импорт).
[Изображение: 5db0a2ab-2729-4c02-b9da-12129cf5eba4.png]
2. Если мы начнём воспроизведение сцены они появятся в правом нижнем углу. Здесь можно увидеть название цеха, индикатор и 9 кнопок: 4 кнопки с видами, Автооблёт, Ночной режим, Скриншот, Сброс кнопка ИНФО. У каждой кнопки есть hotkey и подписан в скобках.

Каждая кнопка с видом при нажатии будет перемещать камеру в заданное место. 
При Автооблёте камера будет крутиться вокруг сцены.
Ночной режим выключает освещение на сцене и изменяет её в тёмно-синие оттенки.
Скриншот делает снимок экрана, при этом панель с кнопками не будет на нём отображена.
Сброс отменяет действия и/или возвращает камеру на начальную позицию.
Также снизу есть показатель FPS, текущее время и дата.
[Изображение: 735e2d8f-bdfe-4376-9bf3-4b924b7a9ecf.png]
3. Кнопка ИНФО при нажатии откроет всплывающее окно с системной информацией и технической поддержкой. При нажатии на кнопку support@winnum.ru пользователя будет переносить в почту и начинать письмо в support.
[Изображение: 4b433863-0d38-474c-8b07-124fee8d7174.png]
4. Внутри ViewButtons в Объект - Действия будет лежать код самой панели.
[Изображение: 169d5b16-6e24-4ce7-8048-f329c056a8bc.png]
5. Чтобы настроить кнопки с видами нужно изменить логику для них в коде. (9-47 строки)

Код:
function ScriptSView() {
    console.log('Общий вид активирован');
    camera.position.set(12.51, 77.63, 66.77);
    camera.rotation.set(-52 * Math.PI / 180, 0, 0);
    camera.updateProjectionMatrix();
    camera.updateMatrix();
    // Убираем вызов updateCoordinates()
    updateStatus('ОБЩИЙ ВИД', '#00ff00');
}

function ScriptView1() {
    console.log('Вид 1 активирован');
    camera.position.set(-54.87, 31.12, 4.72);
    camera.rotation.set(-44 * Math.PI / 180, 0, 0);
    camera.updateProjectionMatrix();
    camera.updateMatrix();
    // Убираем вызов updateCoordinates()
    updateStatus('ВИД 1', '#00ff00');
}

function ScriptView2() {
    console.log('Вид 2 активирован');
    camera.position.set(-23.77, 24.68, -17.76);
    camera.rotation.set(-141 * Math.PI / 180, -37.89 * Math.PI / 180, -153.5 * Math.PI / 180);
    camera.updateProjectionMatrix();
    camera.updateMatrix();
    // Убираем вызов updateCoordinates()
    updateStatus('ВИД 2', '#00ff00');
}

function ScriptView3() {
    console.log('Вид 3 активирован');
    camera.position.set(30.25, 52.22, 44.24);
    camera.rotation.set(-58.2 * Math.PI / 180, 0, 0);
    camera.updateProjectionMatrix();
    camera.updateMatrix();
    // Убираем вызов updateCoordinates()
    updateStatus('ВИД 3', '#00ff00');
}
6. Чтобы изменить названия кнопок видов поменять этот код. (158-164 строки)
Код:
    // Создаем кнопки видов
    var viewButtons = [
        { id: 'btnSView', text: 'ОБЩИЙ ВИД [1]', onClick: ScriptSView },
        { id: 'btnView1', text: 'ВИД 1 [2]', onClick: ScriptView1 },
        { id: 'btnView2', text: 'ВИД 2 [3]', onClick: ScriptView2 },
        { id: 'btnView3', text: 'ВИД 3 [4]', onClick: ScriptView3 },
    ];
7. Чтобы изменить название цеха нужно поменять этот код. (131-132 строки)
Код:
    var header = document.createElement("div");
    header.innerText = 'ЦЕХ 058';
8. Чтобы изменить Автооблёт нужно поменять этот код. (333-337 строки)
Код:
function startAnimation() {
    var startTime = Date.now();
    var radius = 80;
    var height = 60;
    var speed = 0.3;
9. Чтобы изменить Системную информацию в сплывающем окне нужно поменять этот код. (618-633 строки)
Код:
    // Текст информации
    var infoText = document.createElement("div");
    infoText.innerHTML =
        '<p style="margin-bottom: 12px; line-height: 1.5; font-size: 14px; color: #00cccc;">' +
            '<strong style="color: #00ff00;">3D-СЦЕНА:</strong> ЦЕХ 058' +
        '</p>' +
        '<p style="margin-bottom: 12px; line-height: 1.5; font-size: 14px; color: #00cccc;">' +
            '<strong style="color: #00ff00;">РАЗРАБОТЧИК:</strong> ПОНЯТОВ НИКИТА' +
        '</p>' +
        '<p style="margin-bottom: 12px; line-height: 1.5; font-size: 14px; color: #00cccc;">' +
            '<strong style="color: #00ff00;">ВЕРСИЯ:</strong> 2.0' +
        '</p>' +
        '<p style="margin-bottom: 15px; line-height: 1.5; font-size: 14px; color: #00cccc;">' +
            '<strong style="color: #00ff00;">СТАТУС:</strong> СИСТЕМА АКТИВНА' +
        '</p>';
    modal.appendChild(infoText);
Полный код:
Код:
var SView, View1, View2, View3, View4;
var currentAnimation = null;
var isNightMode = false;
var frameCount = 0;
var lastTime = performance.now();
var fps = 0;

// Обновите функции видов для отображения правильных углов
function ScriptSView() {
    console.log('Общий вид активирован');
    camera.position.set(12.51, 77.63, 66.77);
    camera.rotation.set(-52 * Math.PI / 180, 0, 0);
    camera.updateProjectionMatrix();
    camera.updateMatrix();
    // Убираем вызов updateCoordinates()
    updateStatus('ОБЩИЙ ВИД', '#00ff00');
}

function ScriptView1() {
    console.log('Вид 1 активирован');
    camera.position.set(-54.87, 31.12, 4.72);
    camera.rotation.set(-44 * Math.PI / 180, 0, 0);
    camera.updateProjectionMatrix();
    camera.updateMatrix();
    // Убираем вызов updateCoordinates()
    updateStatus('ВИД 1', '#00ff00');
}

function ScriptView2() {
    console.log('Вид 2 активирован');
    camera.position.set(-23.77, 24.68, -17.76);
    camera.rotation.set(-141 * Math.PI / 180, -37.89 * Math.PI / 180, -153.5 * Math.PI / 180);
    camera.updateProjectionMatrix();
    camera.updateMatrix();
    // Убираем вызов updateCoordinates()
    updateStatus('ВИД 2', '#00ff00');
}

function ScriptView3() {
    console.log('Вид 3 активирован');
    camera.position.set(30.25, 52.22, 44.24);
    camera.rotation.set(-58.2 * Math.PI / 180, 0, 0);
    camera.updateProjectionMatrix();
    camera.updateMatrix();
    // Убираем вызов updateCoordinates()
    updateStatus('ВИД 3', '#00ff00');
}

function start() {
    createButtonContainer();
    SView = document.getElementById('btnSView');
    View1 = document.getElementById('btnView1');
    View2 = document.getElementById('btnView2');
    View3 = document.getElementById('btnView3');
    View4 = document.getElementById('btnView4');
    initHotkeys();
    // Запускаем подсчет FPS
    requestAnimationFrame(updateFPS);
}

// Функция для добавления техно-деталей ЧПУ
function addCNCDetails(container) {
    // Добавляем угловые элементы
    const corners = [
        { top: '0', left: '0', transform: 'none' },
        { top: '0', right: '0', transform: 'scaleX(-1)' },
        { bottom: '0', left: '0', transform: 'scaleY(-1)' },
        { bottom: '0', right: '0', transform: 'scale(-1)' }
    ];

    corners.forEach(corner => {
        var cornerElement = document.createElement("div");
        cornerElement.style.position = 'absolute';
        cornerElement.style.width = '15px';
        cornerElement.style.height = '15px';
        cornerElement.style.background = 'linear-gradient(135deg, #00cccc, #008080)';
        cornerElement.style.border = '1px solid #00ffff';
        cornerElement.style.boxShadow = '0 0 5px rgba(0, 255, 255, 0.4)';
        cornerElement.style.transform = corner.transform;
        Object.assign(cornerElement.style, corner);
        container.appendChild(cornerElement);
    });

    // Добавляем техно-сетку на фон
    var gridOverlay = document.createElement("div");
    gridOverlay.style.position = 'absolute';
    gridOverlay.style.top = '0';
    gridOverlay.style.left = '0';
    gridOverlay.style.width = '100%';
    gridOverlay.style.height = '100%';
    gridOverlay.style.backgroundImage = 'linear-gradient(to right, rgba(0, 204, 204, 0.1) 1px, transparent 1px), linear-gradient(to bottom, rgba(0, 204, 204, 0.1) 1px, transparent 1px)';
    gridOverlay.style.backgroundSize = '10px 10px';
    gridOverlay.style.pointerEvents = 'none';
    gridOverlay.style.zIndex = '1';
    container.appendChild(gridOverlay);
}

function createButtonContainer() {
    var sceneView = document.querySelector("#player > div");
   
    // Создаем основной контейнер в стиле ЧПУ станка
    var mainContainer = document.createElement("div");
    mainContainer.style.position = "absolute";
    mainContainer.style.right = '20px';
    mainContainer.style.bottom = '20px';
    mainContainer.style.zIndex = '1000';
    mainContainer.style.background = 'linear-gradient(145deg, #1e1e1e, #2d2d2d)';
    mainContainer.style.border = '3px solid #404040';
    mainContainer.style.borderRadius = '2px';
    mainContainer.style.padding = '15px';
    mainContainer.style.fontFamily = '"Share Tech Mono", "Courier New", monospace';
    mainContainer.style.minWidth = '250px';
    mainContainer.style.overflow = 'hidden';
    mainContainer.style.boxShadow = '0 0 15px rgba(0, 255, 255, 0.2), inset 0 0 10px rgba(0, 0, 0, 0.5)';

    // Добавляем техно-детали
    addCNCDetails(mainContainer);

    // Верхняя панель с названием и статусом
    var headerPlate = document.createElement("div");
    headerPlate.style.background = 'linear-gradient(to bottom, #00b3b3, #008080)';
    headerPlate.style.border = '2px solid #00cccc';
    headerPlate.style.borderRadius = '1px';
    headerPlate.style.padding = '8px 15px';
    headerPlate.style.marginBottom = '15px';
    headerPlate.style.textAlign = 'center';
    headerPlate.style.position = 'relative';
    headerPlate.style.boxShadow = 'inset 0 0 10px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.5)';
    headerPlate.style.textShadow = '0 0 5px rgba(0, 255, 255, 0.7)';
   
    var header = document.createElement("div");
    header.innerText = 'ЦЕХ 058';
    header.style.color = '#00ffff';
    header.style.fontSize = '16px';
    header.style.fontWeight = 'bold';
    header.style.textTransform = 'uppercase';
    header.style.letterSpacing = '3px';
    header.style.marginBottom = '5px';
   
    // Статус бар
    var statusBar = document.createElement("div");
    statusBar.id = 'status-bar';
    statusBar.innerHTML = '<span style="color:#00ff00">■</span> СИСТЕМА АКТИВНА';
    statusBar.style.fontSize = '10px';
    statusBar.style.color = '#00ff00';
   
    headerPlate.appendChild(header);
    headerPlate.appendChild(statusBar);
    mainContainer.appendChild(headerPlate);

    // Контейнер для кнопок
    var btnContainer = document.createElement("div");
    btnContainer.style.display = 'flex';
    btnContainer.style.flexDirection = 'column';
    btnContainer.style.gap = '8px';
    mainContainer.appendChild(btnContainer);

    // Создаем кнопки видов
    var viewButtons = [
        { id: 'btnSView', text: 'ОБЩИЙ ВИД [1]', onClick: ScriptSView },
        { id: 'btnView1', text: 'ВИД 1 [2]', onClick: ScriptView1 },
        { id: 'btnView2', text: 'ВИД 2 [3]', onClick: ScriptView2 },
        { id: 'btnView3', text: 'ВИД 3 [4]', onClick: ScriptView3 },
    ];

    viewButtons.forEach(button => {
        var btn = createCNCButton(button.text, function() { button.onClick(); }, button.id);
        btnContainer.appendChild(btn);
    });

    // Разделитель
    var separator = document.createElement("div");
    separator.style.height = '1px';
    separator.style.background = 'linear-gradient(to right, transparent, #00cccc, transparent)';
    separator.style.margin = '10px 0';
    separator.style.opacity = '0.5';
    btnContainer.appendChild(separator);

    // Дополнительные функциональные кнопки
    var funcButtons = [
        { id: 'btnAnimation', text: 'АВТООБЛЕТ [A]', onClick: toggleAnimation },
        { id: 'btnNightMode', text: 'НОЧНОЙ РЕЖИМ [N]', onClick: toggleNightMode },
        { id: 'btnScreenshot', text: 'СКРИНШОТ [P]', onClick: takeScreenshot },
        { id: 'btnReset', text: 'СБРОС [R]', onClick: resetView },
        { id: 'btnInfo', text: 'ИНФО [I]', onClick: showInfoModal }
    ];

    funcButtons.forEach(button => {
        var btn = createCNCButton(button.text, button.onClick, button.id);
        if (button.id === 'btnInfo') {
            styleEmergencyButton(btn);
        }
        btnContainer.appendChild(btn);
    });

// Панель информации (убираем coordDisplay)
var infoPanel = document.createElement("div");
infoPanel.style.marginTop = '15px';
infoPanel.style.padding = '5px';
infoPanel.style.background = 'rgba(0, 0, 0, 0.3)';
infoPanel.style.border = '1px solid #00cccc';
infoPanel.style.borderRadius = '1px';
infoPanel.style.textAlign = 'center';
infoPanel.style.fontSize = '10px';
infoPanel.style.color = '#00cccc';

// Убираем создание coordDisplay
// var coordDisplay = document.createElement("div");
// coordDisplay.id = 'coord-display';
// coordDisplay.textContent = 'X: 0.0 Y: 0.0 Z: 0.0';
// infoPanel.appendChild(coordDisplay);

var fpsDisplay = document.createElement("div");
fpsDisplay.id = 'fps-display';
fpsDisplay.textContent = 'FPS: 0';
fpsDisplay.style.marginTop = '3px';
infoPanel.appendChild(fpsDisplay);

var timeDisplay = document.createElement("div");
timeDisplay.id = 'time-display';
timeDisplay.style.marginTop = '3px';
infoPanel.appendChild(timeDisplay);

var dateDisplay = document.createElement("div");
dateDisplay.id = 'date-display';
dateDisplay.style.fontSize = '9px';
dateDisplay.style.opacity = '0.7';
dateDisplay.style.marginTop = '3px';
infoPanel.appendChild(dateDisplay);

btnContainer.appendChild(infoPanel);

    // Нижняя техно-полоса
    var footerStrip = document.createElement("div");
    footerStrip.style.height = '3px';
    footerStrip.style.background = 'linear-gradient(to right, #00cccc, #008080, #00cccc)';
    footerStrip.style.marginTop = '15px';
    footerStrip.style.borderRadius = '1px';
    footerStrip.style.boxShadow = '0 0 5px rgba(0, 255, 255, 0.3)';
    mainContainer.appendChild(footerStrip);

    sceneView.appendChild(mainContainer);
   
    // Запускаем обновление времени и координат
    updateTime();
    updateCoordinates();
    setInterval(updateTime, 1000);
    setInterval(updateCoordinates, 100);
}

// Создание универсальной кнопки
function createCNCButton(text, onClick, id) {
    var btn = document.createElement("BUTTON");
    btn.id = id;
    btn.innerText = text;
    styleCNCButton(btn);
    btn.onclick = onClick;
    return btn;
}

// Новый функционал: Ночной режим
function toggleNightMode() {
    isNightMode = !isNightMode;
   
    if (isNightMode) {
        // Сохраняем оригинальные настройки освещения
        if (!window.originalLights) {
            window.originalLights = [];
            scene.traverse(function(object) {
                if (object.isLight) {
                    window.originalLights.push({
                        object: object,
                        intensity: object.intensity,
                        visible: object.visible
                    });
                }
            });
        }
       
        // Уменьшаем интенсивность света
        scene.traverse(function(object) {
            if (object.isLight) {
                object.intensity *= 0.3;
            }
        });
       
        // Добавляем синее ночное освещение
        if (!window.nightLight) {
            window.nightLight = new THREE.AmbientLight(0x003366, 0.5);
            scene.add(window.nightLight);
        }
       
        updateStatus('НОЧНОЙ РЕЖИМ', '#0066cc');
    } else {
        // Восстанавливаем оригинальное освещение
        if (window.originalLights) {
            window.originalLights.forEach(light => {
                light.object.intensity = light.intensity;
                light.object.visible = light.visible;
            });
        }
       
        // Убираем ночное освещение
        if (window.nightLight) {
            scene.remove(window.nightLight);
            window.nightLight = null;
        }
       
        updateStatus('ДНЕВНОЙ РЕЖИМ', '#00ff00');
    }
}

// Новый функционал: Сброс вида
function resetView() {
    if (currentAnimation) {
        stopAnimation();
    }
    ScriptSView();
    updateStatus('ВИД СБРОШЕН', '#00ff00');
}

// Новый функционал: АВТООБЛЕТ
function toggleAnimation() {
    if (currentAnimation) {
        stopAnimation();
        updateStatus('АВТООБЛЕТ ОСТАНОВЛЕН', '#ff4444');
    } else {
        startAnimation();
        updateStatus('АВТООБЛЕТ АКТИВЕН', '#00ff00');
    }
}

function startAnimation() {
    var startTime = Date.now();
    var radius = 80;
    var height = 60;
    var speed = 0.3;
   
    // Сохраняем начальную позицию для возврата
    if (!window.originalCameraPosition) {
        window.originalCameraPosition = camera.position.clone();
        window.originalCameraRotation = camera.rotation.clone();
    }
   
    currentAnimation = function() {
        var time = (Date.now() - startTime) * 0.001 * speed;
        camera.position.x = Math.cos(time) * radius;
        camera.position.z = Math.sin(time) * radius;
        camera.position.y = height + Math.sin(time * 0.5) * 15;
        camera.lookAt(new THREE.Vector3(0, 20, 0));
        camera.updateMatrix();
       
        if (currentAnimation) {
            requestAnimationFrame(currentAnimation);
        }
    };
    currentAnimation();
}

function stopAnimation() {
    currentAnimation = null;
    // Возвращаем камеру в исходное положение
    if (window.originalCameraPosition) {
        camera.position.copy(window.originalCameraPosition);
        camera.rotation.copy(window.originalCameraRotation);
        camera.updateMatrix();
    }
}

// Новый функционал: Скриншот
function takeScreenshot() {
    try {
        renderer.render(scene, camera);
        var imageData = renderer.domElement.toDataURL('image/png');
       
        var link = document.createElement('a');
        link.href = imageData;
        link.download = 'cnc_screenshot_' + new Date().toISOString().replace(/:/g, '-') + '.png';
        link.click();
       
        updateStatus('СКРИНШОТ СОХРАНЕН', '#00ff00');
        setTimeout(() => updateStatus('СИСТЕМА АКТИВНА', '#00ff00'), 2000);
    } catch (error) {
        updateStatus('ОШИБКА СКРИНШОТА', '#ff4444');
    }
}

// Обновление статус бара
function updateStatus(message, color) {
    var statusBar = document.getElementById('status-bar');
    if (statusBar) {
        statusBar.innerHTML = '<span style="color:' + color + '">■</span> ' + message;
        statusBar.style.color = color;
    }
}

// Обновление времени
function updateTime() {
    var now = new Date();
    var timeDisplay = document.getElementById('time-display');
    var dateDisplay = document.getElementById('date-display');
   
    if (timeDisplay) {
        timeDisplay.textContent = now.toLocaleTimeString();
    }
    if (dateDisplay) {
        dateDisplay.textContent = now.toLocaleDateString();
    }
}

// Обновление координат камеры
// Альтернатива если THREE.Math.radToDeg не работает
function updateCoordinates() {
    var coordDisplay = document.getElementById('coord-display');
    if (coordDisplay && camera) {
        // Ручное преобразование радиан в градусы
        var radToDeg = function(rad) {
            return (rad * (180 / Math.PI) + 360) % 360;
        };
       
        var euler = new THREE.Euler();
        euler.setFromQuaternion(camera.quaternion, 'YXZ');
       
        var pitch = radToDeg(euler.x);
        var yaw = radToDeg(euler.y);
        var roll = radToDeg(euler.z);
       
        // Агрессивное округление
        var x = Math.round(camera.position.x);
        var y = Math.round(camera.position.y);
        var z = Math.round(camera.position.z);
       
        coordDisplay.innerHTML =
            '<div style="margin-bottom: 3px; color: #00cccc;">' +
            'ПОЗИЦИЯ: X: ' + x + ' Y: ' + y + ' Z: ' + z +
            '</div>' +
            '<div style="color: #00ff99;">' +
            'ПОВОРОТ: Pitch: ' + Math.round(pitch) + '° ' +
            'Yaw: ' + Math.round(yaw) + '° ' +
            'Roll: ' + Math.round(roll) + '°' +
            '</div>';
    }
}


// Обновление FPS
function updateFPS() {
    frameCount++;
    var currentTime = performance.now();
   
    if (currentTime - lastTime >= 1000) {
        fps = Math.round((frameCount * 1000) / (currentTime - lastTime));
        frameCount = 0;
        lastTime = currentTime;
       
        var fpsDisplay = document.getElementById('fps-display');
        if (fpsDisplay) {
            fpsDisplay.textContent = 'FPS: ' + fps;
            // Меняем цвет в зависимости от FPS
            if (fps < 30) {
                fpsDisplay.style.color = '#ff4444';
            } else if (fps < 50) {
                fpsDisplay.style.color = '#ff9900';
            } else {
                fpsDisplay.style.color = '#00ff00';
            }
        }
    }
   
    requestAnimationFrame(updateFPS);
}

// Горячие клавиши
function initHotkeys() {
    document.addEventListener('keydown', function(event) {
        switch(event.key.toLowerCase()) {
            case 'a': toggleAnimation(); break;
            case 'n': toggleNightMode(); break;
            case 'p': takeScreenshot(); break;
            case 'r': resetView(); break;
            case 'i': showInfoModal(); break;
            case '1': ScriptSView(); break;
            case '2': ScriptView1(); break;
            case '3': ScriptView2(); break;
            case '4': ScriptView3(); break;
        }
    });
}

// Функция для стилизации аварийной кнопки INFO
function styleEmergencyButton(btn) {
    btn.style.fontSize = '11px';
    btn.style.padding = '8px 12px';
    btn.style.color = '#fff';
    btn.style.background = 'linear-gradient(to bottom, #ff4444, #cc0000)';
    btn.style.border = '2px solid #ff6666';
    btn.style.borderRadius = '1px';
    btn.style.cursor = 'pointer';
    btn.style.transition = 'all 0.2s ease';
    btn.style.fontWeight = 'bold';
    btn.style.textTransform = 'uppercase';
    btn.style.letterSpacing = '0.5px';
    btn.style.textShadow = '0 0 2px rgba(255, 0, 0, 0.8)';
    btn.style.fontFamily = '"Share Tech Mono", "Courier New", monospace';
    btn.style.boxShadow = 'inset 0 0 3px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.3)';
    btn.style.width = '100%';
    btn.style.marginBottom = '2px';

    btn.onmouseenter = function () {
        btn.style.background = 'linear-gradient(to bottom, #ff6666, #ff0000)';
        btn.style.boxShadow = 'inset 0 0 5px rgba(0, 0, 0, 0.3), 0 0 8px rgba(255, 0, 0, 0.4)';
        btn.style.transform = 'scale(1.05)';
    };
   
    btn.onmouseleave = function () {
        btn.style.background = 'linear-gradient(to bottom, #ff4444, #cc0000)';
        btn.style.boxShadow = 'inset 0 0 3px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.3)';
        btn.style.transform = 'scale(1)';
    };
   
    btn.onmousedown = function () {
        btn.style.transform = 'scale(0.95)';
        btn.style.boxShadow = 'inset 0 0 5px rgba(0, 0, 0, 0.6), 0 1px 1px rgba(0, 0, 0, 0.2)';
    };
   
    btn.onmouseup = function () {
        btn.style.transform = 'scale(1.05)';
        btn.style.boxShadow = 'inset 0 0 5px rgba(0, 0, 0, 0.3), 0 0 8px rgba(255, 0, 0, 0.4)';
    };
}

// Стилизация кнопок
function styleCNCButton(btn) {
    btn.style.fontSize = '11px';
    btn.style.padding = '8px 12px';
    btn.style.color = '#00ffff';
    btn.style.background = 'linear-gradient(to bottom, #333, #222)';
    btn.style.border = '2px solid #00cccc';
    btn.style.borderRadius = '1px';
    btn.style.cursor = 'pointer';
    btn.style.transition = 'all 0.2s ease';
    btn.style.fontWeight = 'bold';
    btn.style.textTransform = 'uppercase';
    btn.style.letterSpacing = '0.5px';
    btn.style.textShadow = '0 0 2px rgba(0, 255, 255, 0.8)';
    btn.style.fontFamily = '"Share Tech Mono", "Courier New", monospace';
    btn.style.boxShadow = 'inset 0 0 3px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.3)';
    btn.style.width = '100%';
    btn.style.marginBottom = '2px';

    btn.onmouseenter = function () {
        btn.style.background = 'linear-gradient(to bottom, #00cccc, #008080)';
        btn.style.color = '#000';
        btn.style.textShadow = '0 0 2px rgba(255, 255, 255, 0.8)';
        btn.style.boxShadow = 'inset 0 0 5px rgba(0, 0, 0, 0.3), 0 0 8px rgba(0, 204, 204, 0.4)';
    };
   
    btn.onmouseleave = function () {
        btn.style.background = 'linear-gradient(to bottom, #333, ' +
            '#222)';
        btn.style.color = '#00ffff';
        btn.style.textShadow = '0 0 2px rgba(0, 255, 255, 0.8)';
        btn.style.boxShadow = 'inset 0 0 3px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.3)';
    };
   
    btn.onmousedown = function () {
        btn.style.transform = 'translateY(1px)';
        btn.style.boxShadow = 'inset 0 0 5px rgba(0, 0, 0, 0.6), 0 1px 1px rgba(0, 0, 0, 0.2)';
    };
   
    btn.onmouseup = function () {
        btn.style.transform = 'translateY(0)';
        btn.style.boxShadow = 'inset 0 0 3px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.3)';
    };
}

// Функция для показа модального окна в стиле ЧПУ
function showInfoModal() {
    // Создаем overlay
    var overlay = document.createElement("div");
    overlay.style.position = 'fixed';
    overlay.style.top = '0';
    overlay.style.left = '0';
    overlay.style.width = '100%';
    overlay.style.height = '100%';
    overlay.style.background = 'rgba(0, 0, 0, 0.9)';
    overlay.style.zIndex = '2000';
    overlay.style.display = 'flex';
    overlay.style.justifyContent = 'center';
    overlay.style.alignItems = 'center';
   
    // Создаем модальное окно в стиле ЧПУ дисплея
    var modal = document.createElement("div");
    modal.style.background = 'linear-gradient(145deg, #1a1a1a, #2a2a2a)';
    modal.style.border = '3px solid #00cccc';
    modal.style.borderRadius = '2px';
    modal.style.padding = '25px';
    modal.style.maxWidth = '500px';
    modal.style.width = '80%';
    modal.style.boxShadow = '0 0 30px rgba(0, 204, 204, 0.4), inset 0 0 20px rgba(0, 0, 0, 0.6)';
    modal.style.position = 'relative';
    modal.style.color = '#00ffff';
    modal.style.fontFamily = '"Share Tech Mono", "Courier New", monospace';
    modal.style.textShadow = '0 0 3px rgba(0, 255, 255, 0.7)';

    // Заголовок модального окна
    var title = document.createElement("h2");
    title.textContent = 'СИСТЕМНАЯ ИНФОРМАЦИЯ';
    title.style.color = '#00ff00';
    title.style.marginBottom = '20px';
    title.style.textAlign = 'center';
    title.style.fontSize = '18px';
    title.style.fontWeight = 'bold';
    title.style.textTransform = 'uppercase';
    title.style.letterSpacing = '2px';
    modal.appendChild(title);
   
    // Текст информации
    var infoText = document.createElement("div");
    infoText.innerHTML =
        '<p style="margin-bottom: 12px; line-height: 1.5; font-size: 14px; color: #00cccc;">' +
            '<strong style="color: #00ff00;">3D-СЦЕНА:</strong> ЦЕХ 058' +
        '</p>' +
        '<p style="margin-bottom: 12px; line-height: 1.5; font-size: 14px; color: #00cccc;">' +
            '<strong style="color: #00ff00;">РАЗРАБОТЧИК:</strong> ПОНЯТОВ НИКИТА' +
        '</p>' +
        '<p style="margin-bottom: 12px; line-height: 1.5; font-size: 14px; color: #00cccc;">' +
            '<strong style="color: #00ff00;">ВЕРСИЯ:</strong> 2.0' +
        '</p>' +
        '<p style="margin-bottom: 15px; line-height: 1.5; font-size: 14px; color: #00cccc;">' +
            '<strong style="color: #00ff00;">СТАТУС:</strong> СИСТЕМА АКТИВНА' +
        '</p>';
    modal.appendChild(infoText);
   
    // Контейнер для контактов
    var contactContainer = document.createElement("div");
    contactContainer.style.textAlign = 'center';
    contactContainer.style.marginBottom = '20px';
    contactContainer.style.padding = '12px';
    contactContainer.style.background = 'linear-gradient(145deg, #0d2b2b, #1a4040)';
    contactContainer.style.border = '1px solid #00cccc';
    contactContainer.style.borderRadius = '1px';
    contactContainer.style.boxShadow = 'inset 0 0 10px rgba(0, 0, 0, 0.4)';
   
    var contactTitle = document.createElement("div");
    contactTitle.textContent = 'ТЕХНИЧЕСКАЯ ПОДДЕРЖКА';
    contactTitle.style.color = '#00ff00';
    contactTitle.style.marginBottom = '8px';
    contactTitle.style.fontSize = '12px';
    contactTitle.style.fontWeight = 'bold';
    contactContainer.appendChild(contactTitle);
   
    // Ссылка на email
    var emailLink = document.createElement("a");
    emailLink.href = 'mailto:support@winnum.ru';
    emailLink.textContent = 'support@winnum.ru';
        emailLink.style.color = '#00ffff';
    emailLink.style.textDecoration = 'none';
    emailLink.style.fontWeight = 'bold';
    emailLink.style.fontSize = '14px';
    emailLink.style.padding = '6px 12px';
    emailLink.style.display = 'inline-block';
    emailLink.style.transition = 'all 0.3s ease';
    emailLink.style.border = '1px solid #00cccc';
    emailLink.style.background = 'rgba(0, 204, 204, 0.1)';
   
    emailLink.onmouseenter = function() {
        emailLink.style.background = 'rgba(0, 204, 204, 0.3)';
        emailLink.style.color = '#00ff00';
        emailLink.style.boxShadow = '0 0 10px rgba(0, 204, 204, 0.4)';
    };
   
    emailLink.onmouseleave = function() {
        emailLink.style.background = 'rgba(0, 204, 204, 0.1)';
        emailLink.style.color = '#00ffff';
        emailLink.style.boxShadow = 'none';
    };
   
    contactContainer.appendChild(emailLink);
    modal.appendChild(contactContainer);
   
    // Кнопка закрытия
    var closeButton = document.createElement("button");
    closeButton.textContent = 'ЗАКРЫТЬ [ESC]';
    closeButton.style.background = 'linear-gradient(to bottom, #333, #222)';
    closeButton.style.color = '#00cccc';
    closeButton.style.border = '2px solid #00cccc';
    closeButton.style.padding = '8px 20px';
    closeButton.style.borderRadius = '1px';
    closeButton.style.cursor = 'pointer';
    closeButton.style.fontWeight = 'bold';
    closeButton.style.margin = '0 auto';
    closeButton.style.display = 'block';
    closeButton.style.transition = 'all 0.3s ease';
    closeButton.style.fontFamily = '"Share Tech Mono", "Courier New", monospace';
    closeButton.style.textTransform = 'uppercase';
   
    closeButton.onmouseenter = function() {
        closeButton.style.background = 'linear-gradient(to bottom, #00cccc, #008080)';
        closeButton.style.color = '#000';
    };
   
    closeButton.onmouseleave = function() {
        closeButton.style.background = 'linear-gradient(to bottom, ' +
            '#333, #222)';
        closeButton.style.color = '#00cccc';
    };
   
    closeButton.onclick = function() {
        document.body.removeChild(overlay);
    };
   
    modal.appendChild(closeButton);
    overlay.appendChild(modal);
   
    // Добавляем overlay на страницу
    document.body.appendChild(overlay);
   
    // Закрытие по клику на overlay
    overlay.onclick = function(e) {
        if (e.target === overlay) {
            document.body.removeChild(overlay);
        }
    };
   
    // Закрытие по ESC
    document.addEventListener('keydown', function closeModal(e) {
        if (e.key === 'Escape') {
            document.body.removeChild(overlay);
            document.removeEventListener('keydown', closeModal);
        }
    });
}

 



Файлы вложений
.zip   ViewButtons (2).zip (Размер: 18.75 KB / Загрузок: 2)
Распечатать этот элемент

Photo Панель с кнопками для 3D v1
Автор: nponyatov - 09-19-2025, 08:31 AM - Сообщество: Динамические приложения в WINNUM Platform - Нет ответов

В приложении Цифровой двойник довольно часто используются кнопки. Я решил создать панель с кнопками для 3D в стиле ЧПУ-станка. 
В данном посте я хотел бы рассказать о том как добавить их себе в 3D-сцену и настроить. Они смогут помочь стилизовать сцену и улучшить восприятие.

1. Скачать файл ViewButtons (во вложении) и импортировать себе в сцену (Файл - Импорт).
[Изображение: 5db0a2ab-2729-4c02-b9da-12129cf5eba4.png]
2. Если мы начнём воспроизведение сцены они появятся в правом нижнем углу. Здесь можно увидеть название цеха и 5 кнопок: 4 кнопки с видами, кнопка INFO. Каждая кнопка с видом при нажатии будет перемещать камеру в заданное место. 
[Изображение: 62588b92-1006-48ef-a65b-7e2f21572f1a.png]
3. Кнопка INFO при нажатии откроет всплывающее окно с системной информацией и технической поддержкой. При нажатии на кнопку support@winnum.ru пользователя будет переносить в почту и начинать письмо в support.
[Изображение: fc3682c2-550a-46e4-825d-d6620a63d12f.png]
4. Внутри ViewButtons в Объект - Действия будет лежать код самой панели.
[Изображение: 169d5b16-6e24-4ce7-8048-f329c056a8bc.png]
5. Чтобы настроить кнопки с видами нужно изменить логику для них в коде в параметрах по camera.position и camera.rotation. (386-436 строки)

Код:
// Логика для общего вида
function ScriptSView(cButton) {
    console.log('Общий вид активирован');
    camera.position.x = 1.76;
    camera.position.y = 49.84;
    camera.position.z = 39;
    camera.rotation.x = -52.42 * Math.PI / 180;
    camera.rotation.y = 0 * Math.PI / 180;
    camera.rotation.z = 0 * Math.PI / 180;
    camera.updateProjectionMatrix();
    camera.updateMatrix();
}
// Логика для вида 1
function ScriptView1(cButton) {
    console.log('Вид 1 активирован');
    camera.position.x = -41;
    camera.position.y = 32.43;
    camera.position.z = 3.4;
    camera.rotation.x = -58 * Math.PI / 180;
    camera.rotation.y = -37.42 * Math.PI / 180;
    camera.rotation.z = -44.3 * Math.PI / 180;
    camera.updateProjectionMatrix();
    camera.updateMatrix();
}
// Логика для вида 2
function ScriptView2(cButton) {
    console.log('Вид 2 активирован');
    camera.position.x = 27.72;
    camera.position.y = 40.57;
    camera.position.z = 37.53;
    camera.rotation.x = -47.26 * Math.PI / 180;
    camera.rotation.y = 9.58 * Math.PI / 180;
    camera.rotation.z = 10.21 * Math.PI / 180;
    camera.updateProjectionMatrix();
    camera.updateMatrix();
}
// Логика для вида 3
function ScriptView3(cButton) {
    console.log('Вид 3 активирован');
    camera.position.x = -53.74;
    camera.position.y = 28.2;
    camera.position.z = 30.92;
    camera.rotation.x = -44.4 * Math.PI / 180;
    camera.rotation.y = -43.24 * Math.PI / 180;
    camera.rotation.z = -33.86 * Math.PI / 180;
    camera.updateProjectionMatrix();
    camera.updateMatrix();
}
6. Чтобы изменить названия кнопок нужно поменять этот код. (65-71 строки)
Код:
    // Создаем кнопки видов
    var buttons = [
        { id: 'btnSView', text: 'ОБЩИЙ ВИД', onClick: ScriptSView },
        { id: 'btnView1', text: 'ВИД 1', onClick: ScriptView1 },
        { id: 'btnView2', text: 'ВИД 2', onClick: ScriptView2 },
        { id: 'btnView3', text: 'ВИД 3', onClick: ScriptView3 },
    ];
7. Чтобы изменить название цеха нужно поменять этот код. (45-47 строки)
Код:
    // Заголовок "ЦЕХ 9" в стиле ЧПУ дисплея
    var header = document.createElement("div");
    header.innerText = 'ЦЕХ 9';
8. Чтобы изменить Системную информацию в сплывающем окне нужно поменять этот код. (273-285 строки)
Код:
    // Текст информации
    var infoText = document.createElement("div");
    infoText.innerHTML =
        '<p style="margin-bottom: 12px; line-height: 1.5; font-size: 14px; color: #00cccc;">' +
            '<strong style="color: #00ff00;">3D-СЦЕНА:</strong> ЦЕХ 9' +
        '</p>' +
        '<p style="margin-bottom: 12px; line-height: 1.5; font-size: 14px; color: #00cccc;">' +
            '<strong style="color: #00ff00;">РАЗРАБОТЧИК:</strong> ПОНЯТОВ НИКИТА' +
        '</p>' +
        '<p style="margin-bottom: 15px; line-height: 1.5; font-size: 14px; color: #00cccc;">' +
            '<strong style="color: #00ff00;">СТАТУС:</strong> СИСТЕМА АКТИВНА' +
        '</p>';
    modal.appendChild(infoText);

Полный код:
Код:
var SView, View1, View2, View3, View4;

function start() {
    createButtonContainer();
    SView = document.getElementById('btnSView');
    View1 = document.getElementById('btnView1');
    View2 = document.getElementById('btnView2');
    View3 = document.getElementById('btnView3');
    View4 = document.getElementById('btnView4');
}

function createButtonContainer() {
    var sceneView = document.querySelector("#player > div");
   
    // Создаем основной контейнер в стиле ЧПУ станка
    var mainContainer = document.createElement("div");
    mainContainer.style.position = "absolute";
    mainContainer.style.right = '20px';
    mainContainer.style.bottom = '20px';
    mainContainer.style.zIndex = '1000';
    mainContainer.style.background = 'linear-gradient(145deg, #1e1e1e, #2d2d2d)';
    mainContainer.style.border = '3px solid #404040';
    mainContainer.style.borderRadius = '2px';
    mainContainer.style.padding = '15px';
    mainContainer.style.fontFamily = '"Share Tech Mono", "Courier New", monospace';
    mainContainer.style.minWidth = '220px';
    mainContainer.style.overflow = 'hidden';
    mainContainer.style.boxShadow = '0 0 15px rgba(0, 255, 255, 0.2), inset 0 0 10px rgba(0, 0, 0, 0.5)';

    // Добавляем техно-детали
    addCNCDetails(mainContainer);

    // Верхняя панель с названием в стиле ЧПУ дисплея
    var headerPlate = document.createElement("div");
    headerPlate.style.background = 'linear-gradient(to bottom, #00b3b3, #008080)';
    headerPlate.style.border = '2px solid #00cccc';
    headerPlate.style.borderRadius = '1px';
    headerPlate.style.padding = '8px 15px';
    headerPlate.style.marginBottom = '15px';
    headerPlate.style.textAlign = 'center';
    headerPlate.style.position = 'relative';
    headerPlate.style.boxShadow = 'inset 0 0 10px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.5)';
    headerPlate.style.textShadow = '0 0 5px rgba(0, 255, 255, 0.7)';
   
    // Заголовок "ЦЕХ 9" в стиле ЧПУ дисплея
    var header = document.createElement("div");
    header.innerText = 'ЦЕХ 9';
    header.style.color = '#00ffff';
    header.style.fontSize = '16px';
    header.style.fontWeight = 'bold';
    header.style.textTransform = 'uppercase';
    header.style.letterSpacing = '3px';
    header.style.fontFamily = '"Share Tech Mono", "Courier New", monospace';
   
    headerPlate.appendChild(header);
    mainContainer.appendChild(headerPlate);

    // Контейнер для кнопок
    var btnContainer = document.createElement("div");
    btnContainer.style.display = 'flex';
    btnContainer.style.flexDirection = 'column';
    btnContainer.style.gap = '8px';
    mainContainer.appendChild(btnContainer);

    // Создаем кнопки видов
    var buttons = [
        { id: 'btnSView', text: 'ОБЩИЙ ВИД', onClick: ScriptSView },
        { id: 'btnView1', text: 'ВИД 1', onClick: ScriptView1 },
        { id: 'btnView2', text: 'ВИД 2', onClick: ScriptView2 },
        { id: 'btnView3', text: 'ВИД 3', onClick: ScriptView3 },
    ];

    buttons.forEach(button => {
        var btn = document.createElement("BUTTON");
        btn.name = button.id;
        btn.id = button.id;
        btn.innerText = button.text;
        styleCNCButton(btn);
        btn.onclick = function () { button.onClick(btn); };
        btnContainer.appendChild(btn);
    });

    // Кнопка INFO в стиле аварийной кнопки
    var infoButton = document.createElement("BUTTON");
    infoButton.name = 'btnInfo';
    infoButton.id = 'btnInfo';
    infoButton.innerText = 'INFO';
    styleEmergencyButton(infoButton);
    infoButton.onclick = function () { showInfoModal(); };
    btnContainer.appendChild(infoButton);

    // Нижняя техно-полоса
    var footerStrip = document.createElement("div");
    footerStrip.style.height = '3px';
    footerStrip.style.background = 'linear-gradient(to right, #00cccc, #008080, #00cccc)';
    footerStrip.style.marginTop = '15px';
    footerStrip.style.borderRadius = '1px';
    footerStrip.style.boxShadow = '0 0 5px rgba(0, 255, 255, 0.3)';
    mainContainer.appendChild(footerStrip);

    sceneView.appendChild(mainContainer);
}

// Функция для добавления техно-деталей ЧПУ
function addCNCDetails(container) {
    // Добавляем угловые элементы
    const corners = [
        { top: '0', left: '0', transform: 'none' },
        { top: '0', right: '0', transform: 'scaleX(-1)' },
        { bottom: '0', left: '0', transform: 'scaleY(-1)' },
        { bottom: '0', right: '0', transform: 'scale(-1)' }
    ];

    corners.forEach(corner => {
        var cornerElement = document.createElement("div");
        cornerElement.style.position = 'absolute';
        cornerElement.style.width = '15px';
        cornerElement.style.height = '15px';
        cornerElement.style.background = 'linear-gradient(135deg, #00cccc, #008080)';
        cornerElement.style.border = '1px solid #00ffff';
        cornerElement.style.boxShadow = '0 0 5px rgba(0, 255, 255, 0.4)';
        cornerElement.style.transform = corner.transform;
        Object.assign(cornerElement.style, corner);
        container.appendChild(cornerElement);
    });

    // Добавляем техно-сетку на фон
    var gridOverlay = document.createElement("div");
    gridOverlay.style.position = 'absolute';
    gridOverlay.style.top = '0';
    gridOverlay.style.left = '0';
    gridOverlay.style.width = '100%';
    gridOverlay.style.height = '100%';
    gridOverlay.style.backgroundImage = 'linear-gradient(to right, rgba(0, 204, 204, 0.1) 1px, transparent 1px), linear-gradient(to bottom, rgba(0, 204, 204, 0.1) 1px, transparent 1px)';
    gridOverlay.style.backgroundSize = '10px 10px';
    gridOverlay.style.pointerEvents = 'none';
    gridOverlay.style.zIndex = '1';
    container.appendChild(gridOverlay);
}

// Функция для стилизации обычных кнопок ЧПУ
function styleCNCButton(btn) {
    btn.style.fontSize = '12px';
    btn.style.padding = '10px 15px';
    btn.style.color = '#00ffff';
    btn.style.background = 'linear-gradient(to bottom, #333, #222)';
    btn.style.border = '2px solid #00cccc';
    btn.style.borderRadius = '1px';
    btn.style.cursor = 'pointer';
    btn.style.transition = 'all 0.2s ease';
    btn.style.fontWeight = 'bold';
    btn.style.textTransform = 'uppercase';
    btn.style.letterSpacing = '1px';
    btn.style.textShadow = '0 0 3px rgba(0, 255, 255, 0.8)';
    btn.style.fontFamily = '"Share Tech Mono", "Courier New", monospace';
    btn.style.boxShadow = 'inset 0 0 5px rgba(0, 0, 0, 0.5), 0 2px 4px rgba(0, 0, 0, 0.3)';

    // Эффект на hover
    btn.onmouseenter = function () {
        btn.style.background = 'linear-gradient(to bottom, #00cccc, #008080)';
        btn.style.color = '#000';
        btn.style.textShadow = '0 0 3px rgba(255, 255, 255, 0.8)';
        btn.style.boxShadow = 'inset 0 0 10px rgba(0, 0, 0, 0.3), 0 0 15px rgba(0, 204, 204, 0.4)';
    };
   
    btn.onmouseleave = function () {
        btn.style.background = 'linear-gradient(to bottom, #333, #222)';
        btn.style.color = '#00ffff';
        btn.style.textShadow = '0 0 3px rgba(0, 255, 255, 0.8)';
        btn.style.boxShadow = 'inset 0 0 5px rgba(0, 0, 0, 0.5), 0 2px 4px rgba(0, 0, 0, 0.3)';
    };
   
    // Эффект при нажатии
    btn.onmousedown = function () {
        btn.style.transform = 'translateY(1px)';
        btn.style.boxShadow = 'inset 0 0 8px rgba(0, 0, 0, 0.6), 0 1px 2px rgba(0, 0, 0, 0.3)';
    };
   
    btn.onmouseup = function () {
        btn.style.transform = 'translateY(0)';
        btn.style.boxShadow = 'inset 0 0 5px rgba(0, 0, 0, 0.5), 0 2px 4px rgba(0, 0, 0, 0.3)';
    };
}

// Функция для стилизации аварийной кнопки INFO
function styleEmergencyButton(btn) {
    btn.style.fontSize = '12px';
    btn.style.padding = '10px 15px';
    btn.style.color = '#fff';
    btn.style.background = 'linear-gradient(to bottom, #ff4444, #cc0000)';
    btn.style.border = '2px solid #ff6666';
    btn.style.borderRadius = '1px';
    btn.style.cursor = 'pointer';
    btn.style.transition = 'all 0.2s ease';
    btn.style.fontWeight = 'bold';
    btn.style.textTransform = 'uppercase';
    btn.style.letterSpacing = '1px';
    btn.style.textShadow = '0 0 3px rgba(255, 0, 0, 0.8)';
    btn.style.fontFamily = '"Share Tech Mono", "Courier New", monospace';
    btn.style.boxShadow = 'inset 0 0 5px rgba(0, 0, 0, 0.5), 0 2px 4px rgba(0, 0, 0, 0.3)';
    btn.style.marginTop = '10px';

    // Эффект на hover
    btn.onmouseenter = function () {
        btn.style.background = 'linear-gradient(to bottom, #ff6666, #ff0000)';
        btn.style.boxShadow = 'inset 0 0 10px rgba(0, 0, 0, 0.3), 0 0 15px rgba(255, 0, 0, 0.4)';
        btn.style.transform = 'scale(1.05)';
    };
   
    btn.onmouseleave = function () {
        btn.style.background = 'linear-gradient(to bottom, #ff4444, #cc0000)';
        btn.style.boxShadow = 'inset 0 0 5px rgba(0, 0, 0, 0.5), 0 2px 4px rgba(0, 0, 0, 0.3)';
        btn.style.transform = 'scale(1)';
    };
   
    // Эффект при нажатии
    btn.onmousedown = function () {
        btn.style.transform = 'scale(0.95)';
        btn.style.boxShadow = 'inset 0 0 8px rgba(0, 0, 0, 0.6), 0 1px 2px rgba(0, 0, 0, 0.3)';
    };
   
    btn.onmouseup = function () {
        btn.style.transform = 'scale(1.05)';
        btn.style.boxShadow = 'inset 0 0 10px rgba(0, 0, 0, 0.3), 0 0 15px rgba(255, 0, 0, 0.4)';
    };
}

// Функция для показа модального окна в стиле ЧПУ
function showInfoModal() {
    // Создаем overlay
    var overlay = document.createElement("div");
    overlay.style.position = 'fixed';
    overlay.style.top = '0';
    overlay.style.left = '0';
    overlay.style.width = '100%';
    overlay.style.height = '100%';
    overlay.style.background = 'rgba(0, 0, 0, 0.9)';
    overlay.style.zIndex = '2000';
    overlay.style.display = 'flex';
    overlay.style.justifyContent = 'center';
    overlay.style.alignItems = 'center';
    overlay.style.backdropFilter = 'blur(3px)';
   
    // Создаем модальное окно в стиле ЧПУ дисплея
    var modal = document.createElement("div");
    modal.style.background = 'linear-gradient(145deg, #1a1a1a, #2a2a2a)';
    modal.style.border = '3px solid #00cccc';
    modal.style.borderRadius = '2px';
    modal.style.padding = '25px';
    modal.style.maxWidth = '500px';
    modal.style.width = '80%';
    modal.style.boxShadow = '0 0 30px rgba(0, 204, 204, 0.4), inset 0 0 20px rgba(0, 0, 0, 0.6)';
    modal.style.position = 'relative';
    modal.style.color = '#00ffff';
    modal.style.fontFamily = '"Share Tech Mono", "Courier New", monospace';
    modal.style.textShadow = '0 0 3px rgba(0, 255, 255, 0.7)';

    // Добавляем техно-детали к модальному окну
    addCNCDetails(modal);

    // Заголовок модального окна
    var title = document.createElement("h2");
    title.textContent = 'СИСТЕМНАЯ ИНФОРМАЦИЯ';
    title.style.color = '#00ff00';
    title.style.marginBottom = '20px';
    title.style.textAlign = 'center';
    title.style.fontSize = '18px';
    title.style.fontWeight = 'bold';
    title.style.textTransform = 'uppercase';
    title.style.letterSpacing = '2px';
    modal.appendChild(title);
   
    // Текст информации
    var infoText = document.createElement("div");
    infoText.innerHTML =
        '<p style="margin-bottom: 12px; line-height: 1.5; font-size: 14px; color: #00cccc;">' +
            '<strong style="color: #00ff00;">3D-СЦЕНА:</strong> ЦЕХ 9' +
        '</p>' +
        '<p style="margin-bottom: 12px; line-height: 1.5; font-size: 14px; color: #00cccc;">' +
            '<strong style="color: #00ff00;">РАЗРАБОТЧИК:</strong> ПОНЯТОВ НИКИТА' +
        '</p>' +
        '<p style="margin-bottom: 15px; line-height: 1.5; font-size: 14px; color: #00cccc;">' +
            '<strong style="color: #00ff00;">СТАТУС:</strong> СИСТЕМА АКТИВНА' +
        '</p>';
    modal.appendChild(infoText);
   
    // Контейнер для контактов
    var contactContainer = document.createElement("div");
    contactContainer.style.textAlign = 'center';
    contactContainer.style.marginBottom = '20px';
    contactContainer.style.padding = '12px';
    contactContainer.style.background = 'linear-gradient(145deg, #0d2b2b, #1a4040)';
    contactContainer.style.border = '1px solid #00cccc';
    contactContainer.style.borderRadius = '1px';
    contactContainer.style.boxShadow = 'inset 0 0 10px rgba(0, 0, 0, 0.4)';
   
    var contactTitle = document.createElement("div");
    contactTitle.textContent = 'ТЕХНИЧЕСКАЯ ПОДДЕРЖКА';
    contactTitle.style.color = '#00ff00';
    contactTitle.style.marginBottom = '8px';
    contactTitle.style.fontSize = '12px';
    contactTitle.style.fontWeight = 'bold';
    contactContainer.appendChild(contactTitle);
   
    // Ссылка на email
    var emailLink = document.createElement("a");
    emailLink.href = 'mailto:support@winnum.ru';
    emailLink.textContent = 'support@winnum.ru';
    emailLink.style.color = '#00ffff';
    emailLink.style.textDecoration = 'none';
    emailLink.style.fontWeight = 'bold';
    emailLink.style.fontSize = '14px';
    emailLink.style.padding = '6px 12px';
    emailLink.style.display = 'inline-block';
    emailLink.style.transition = 'all 0.3s ease';
    emailLink.style.border = '1px solid #00cccc';
    emailLink.style.background = 'rgba(0, 204, 204, 0.1)';
   
    emailLink.onmouseenter = function() {
        emailLink.style.background = 'rgba(0, 204, 204, 0.3)';
        emailLink.style.color = '#00ff00';
        emailLink.style.boxShadow = '0 0 10px rgba(0, 204, 204, 0.4)';
    };
   
    emailLink.onmouseleave = function() {
        emailLink.style.background = 'rgba(0, 204, 204, 0.1)';
        emailLink.style.color = '#00ffff';
        emailLink.style.boxShadow = 'none';
    };
   
    contactContainer.appendChild(emailLink);
    modal.appendChild(contactContainer);
   
    // Кнопка закрытия
    var closeButton = document.createElement("button");
    closeButton.textContent = 'ЗАКРЫТЬ [ESC]';
    closeButton.style.background = 'linear-gradient(to bottom, #333, #222)';
    closeButton.style.color = '#00cccc';
    closeButton.style.border = '2px solid #00cccc';
    closeButton.style.padding = '8px 20px';
    closeButton.style.borderRadius = '1px';
    closeButton.style.cursor = 'pointer';
    closeButton.style.fontWeight = 'bold';
    closeButton.style.margin = '0 auto';
    closeButton.style.display = 'block';
    closeButton.style.transition = 'all 0.3s ease';
    closeButton.style.fontFamily = '"Share Tech Mono", "Courier New", monospace';
    closeButton.style.textTransform = 'uppercase';
   
    closeButton.onmouseenter = function() {
        closeButton.style.background = 'linear-gradient(to bottom, #00cccc, #008080)';
        closeButton.style.color = '#000';
    };
   
    closeButton.onmouseleave = function() {
        closeButton.style.background = 'linear-gradient(to bottom, #333, #222)';
        closeButton.style.color = '#00cccc';
    };
   
    closeButton.onclick = function() {
        document.body.removeChild(overlay);
    };
   
    modal.appendChild(closeButton);
    overlay.appendChild(modal);
   
    // Добавляем overlay на страницу
    document.body.appendChild(overlay);
   
    // Закрытие по клику на overlay
    overlay.onclick = function(e) {
        if (e.target === overlay) {
            document.body.removeChild(overlay);
        }
    };
   
    // Закрытие по ESC
    document.addEventListener('keydown', function closeModal(e) {
        if (e.key === 'Escape') {
            document.body.removeChild(overlay);
            document.removeEventListener('keydown', closeModal);
        }
    });
}

// Логика для общего вида
function ScriptSView(cButton) {
    console.log('Общий вид активирован');
    camera.position.x = 1.76;
    camera.position.y = 49.84;
    camera.position.z = 39;
    camera.rotation.x = -52.42 * Math.PI / 180;
    camera.rotation.y = 0 * Math.PI / 180;
    camera.rotation.z = 0 * Math.PI / 180;
    camera.updateProjectionMatrix();
    camera.updateMatrix();
}

// Логика для вида 1
function ScriptView1(cButton) {
    console.log('Вид 1 активирован');
    camera.position.x = -41;
    camera.position.y = 32.43;
    camera.position.z = 3.4;
    camera.rotation.x = -58 * Math.PI / 180;
    camera.rotation.y = -37.42 * Math.PI / 180;
    camera.rotation.z = -44.3 * Math.PI / 180;
    camera.updateProjectionMatrix();
    camera.updateMatrix();
}

// Логика для вида 2
function ScriptView2(cButton) {
    console.log('Вид 2 активирован');
    camera.position.x = 27.72;
    camera.position.y = 40.57;
    camera.position.z = 37.53;
    camera.rotation.x = -47.26 * Math.PI / 180;
    camera.rotation.y = 9.58 * Math.PI / 180;
    camera.rotation.z = 10.21 * Math.PI / 180;
    camera.updateProjectionMatrix();
    camera.updateMatrix();
}

// Логика для вида 3
function ScriptView3(cButton) {
    console.log('Вид 3 активирован');
    camera.position.x = -53.74;
    camera.position.y = 28.2;
    camera.position.z = 30.92;
    camera.rotation.x = -44.4 * Math.PI / 180;
    camera.rotation.y = -43.24 * Math.PI / 180;
    camera.rotation.z = -33.86 * Math.PI / 180;
    camera.updateProjectionMatrix();
    camera.updateMatrix();
}



Файлы вложений
.zip   ViewButtons (2).zip (Размер: 18.8 KB / Загрузок: 1)
Распечатать этот элемент

Photo Добавление библиотеки в приложение
Автор: Lamantur - 08-22-2025, 07:18 AM - Сообщество: Динамические приложения в WINNUM Platform - Нет ответов

Добрый день коллеги. 

Сегодня хочу рассказать вам одну интересную тему;
Редактор динамических приложений уже содержит в себе множество библиотек для JavaScript, библиотеки это подготовленный заранее набор функций, который позволяет сократить код, сосредоточившись на выполняемой задаче. Вам не нужно самостоятельно отрисовывать график в формате svg, за вас это может сделать встроенный chart.js, d3.js, c3.js. 

Тем не менее нельзя предусмотреть заранее все задачи, которые могут решаться с помощью динамических приложений, поэтому я расскажу как действовать, если нужная библиотека или дополнение к ней не нашлись в списке библиотек редактора.

Для начала попробуем загрузить библиотеки online, т.е. предполагается, что пользователь может пользоваться интернетом, библиотеки в этом случае загружаются через ссылку.

1. Добавление через HTML
Любой контейнер для этого подойдет, он загружается асинхронно и перейдет по ссылке, инициализировав библиотеку
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
Это самый простой и работающий способ, главное дождаться загрузки элемента перед использованием библиотеки. 
Эта ссылка дана для примера JQuery уже установлен в редакторе, скачивать его повторно не нужно.

2. Добавление через JavaScript

Код:
// Создаем элемент script
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js';

// Добавляем на страницу
document.head.appendChild(script);

// Обработка загрузки
script.onload = function() {
    console.log('Библиотека загружена!');
    // Здесь можно использовать библиотеку
    // $(document).ready(function() { ... });
};

script.onerror = function() {
    console.error('Ошибка загрузки библиотеки');
};

Асинхронная загрузка с Promise :
Код:
function loadScript(src) {
    return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = src;
        script.async = true;
       
        script.onload = () => resolve(script);
        script.onerror = () => reject(new Error(`Ошибка загрузки: ${src}`));
       
        document.head.appendChild(script);
    });
}

// Использование
loadScript('https://cdn.jsdelivivr.net/npm/lodash@4.17.21/lodash.min.js')
    .then(() => {
        console.log('Библиотека загружена');
        // Используем библиотеку
    })
    .catch(error => console.error(error));

Добавление CSS библиотек
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
или
Код:
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css';
document.head.appendChild(link);

bootstrap тоже уже установлен, делать это повторно не нужно



Вдруг, выясняется, что у заказчика изолированная сеть и все эти ссылки не работают. Как быть?
Почти все библиотеки можно скачать себе в медиа библиотеку и использовать как обычные JS или css
Перейдите по ссылке самостоятельно и библиотека откроется в браузере в виде сокращенного JavaScript кода.
Библиотеки специально хранятся в сжатом виде без правил оформления и комментариев, часто используются однобуквенные переменные.

Весь текст скопируйте в файл с расширением .js для JavaScript и .css для стилей.

Загрузите файлы в медиа библиотеку и используйте ссылки на эти файлы вместо ссылок cdn.
В медиа библиотеке предусмотрена отдельная кнопка "скопировать ссылку". Сама ссылка выглядит так:

/Winnum/resources/themes/current/images/app/ui/designer/media/38/createChart.js

Для того чтобы избежать проблем при импорте вашего приложения на другие платформы не стоит использовать прямые ссылки на папки с файлами, они могут измениться.
Ранее мы уже делали это, когда инициализировали наш manifest.js. Повторим еще раз.

Код:
$(window).ready(function(){
    loadResources(true);
});
function loadResources(initial){
    var oid = '';
    var appid = '';
    baseSdkUtils.the = {};
    baseSdkUtils.service.WNFactory.getPersistable(baseApplicationDesignerUtils.getAppid(), function(data){
        if ( !baseSdkUtils.isSuccess(data) ){
            alert('Error: ' + baseSdkUtils.decode(data.innerHTML));
            console.warn(baseSdkUtils.decode(data.innerHTML));
            return;
        }
        /******* Случай, когда код выполняется в приложении ******/
        if ( data.getElementsByTagName('item').length > 0 ){
            oid = data.getElementsByTagName('item')[0].getAttribute('ApplicationInfo__classNameA15');
            oid += ':';
            appid = data.getElementsByTagName('item')[0].getAttribute('ApplicationInfo__idA15');
            oid += appid;
        }
        /****** Случай, когда код выполняется в редакторе ********/
        else{
            oid = baseApplicationDesignerUtils.oid;
            appid = oid.split(':')[1];
        }
        /****** Создание структуры для хранения атрибутов ********/
            baseSdkUtils.the.appoid = baseApplicationDesignerUtils.getAppid();
            baseSdkUtils.the.appid = appid;
            baseSdkUtils.the.oid = oid;
            baseSdkUtils.the.media_url = '/' + baseSdkUtils.appId + '/resources/themes/current/images/app/ui/designer/media/' + appid + '/';       
           
            $('#menu').load(baseSdkUtils.the.media_url + '/menu.html');
        /**** Загрузка js ****************************************/
            jQuery.ajax({
                url: baseSdkUtils.the.media_url + ' createChart.js',
                dataType: 'script',
                success: function(){ loadResourcesProcessor(initial); },
                async: true
            });
    });
}
Вы можете не делать все это повторно, у вас уже есть переменная  baseSdkUtils.the.media_url, которая содержит весь путь до библиотеки, остается указать файл, если он в отдельной папке, это тоже нужно добавить к названию файла.
Помимо описанных способов вы можете так же использовать  ajax, как и в примере, если запускать что либо из библиотеки сразу не нужно, уберите поле success, асинхронный вызов в данном случае тоже не обязателен, если библиотека инициализируется один раз.

Будьте осторожны, некоторые библиотеки влияют на скорость открытия страницы, поищите, возможно есть сокращенная версия библиотеки, которой вам будет достаточно. 

Создавайте свои библиотеки с набором нужных функций и стилей, чтобы использовать их в дальнейших разработках.
Вы можете создать заготовку html элемента неограниченного размера. Добавьте туда все стили и скрипты, которые участвуют непосредственно в элементе и работают локально и переносите ее в новые приложения, вам больше не понадобится создавать весь интерфейс заново:
   

Распечатать этот элемент

Star WinnumAjaxAuthHandler - Пример
Автор: Lamantur - 05-27-2025, 09:15 AM - Сообщество: Динамические приложения в WINNUM Platform - Нет ответов

Всем привет!

Для начала, хотел бы предупредить, что хотя ниже и упоминается конкретно модуль Испытания, но этот "Лайфхак" пригодится в любом другом месте. Поэтому смело окунайтесь в пост.

WINNUM SDK предлагает довольно обширное количество различных методов для разработки приложений. Однако, я недавно натолкнулся на некоторую проблему. В модуле Winnum Испытания мне хотелось получить информацию по испытаниям и вообще пользоваться стандартными отчетами у себя. Испытания предлагают два варианта нахождения прошедших испытаний:
1. Отчет об испытаниях;
2. Отчет по оборудованию;
Второй вариант легко повторить у себя в приложении, для этого не обязательно получать весь отчет. В поле ввода вводится стенд, а так же время, по которому и производится поиск. Не очень трудно догадаться, что выполнив функцию getSignal и указав конкретный сигнал, а можно и сразу несколько - мы получим все данные по прошедшим испытаниям, и даже больше чем в отчете, если нам это надо.
Сложность возникает, когда мы смотрим на Отчет об испытаниях.

   
Huh Huh Huh

Отчет отрабатывает довольно быстро а данные из этой таблицы были бы очень полезны в моем приложении.  Angel
Я хотел найти путь, по которому смог бы что-то получить из этой таблицы, но решение оказалось простым и изящным.
Далее просто описываю путь, повторяйте за мной  Cool

Нажимаем F12

Переходим на вкладку Сеть

   

Далее (выражаясь общими словами) выполняем запрос:

   


Далее нажимаю два раза на запрос в WinnumAjaxAuthHandler и вижу:

   

Big Grin


т.е. если в браузере отправить такой запрос, то я сразу получу все данные для этой таблицы. И даже ссылки и картинки присутствуют в ответе.

Осталось дело за малым - научиться работать с Ajax:

Делаем кнопку и в  нее - событие:


Код:
$('#FC204DC3-2522-438C-B523-09B4C85ABEBF').click(function(event){
const baseUrl = 'http://127.0.0.1/Winnum/servlets/WinnumAjaxAuthHandler';
const params = {
  cmdclass: 'winnum.views.pages.app.tts.reports.TestingSearchHandler',
  cmdmethod: 'performSearch',
  appid: 'winnum.org.app.WNApplicationInstance:1',
  callerId: 'refreshLoadingList',
  barCode: '',
  testingNumber: '',
  year: '2025',
  number: '',
  mode: 'yes',
  order: 'asc',
  offset: '0',
  limit: '100',
  _: ''
};

const url = `${baseUrl}?${new URLSearchParams(params).toString()}`;

fetch(url)
  .then(response => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json(); // или response.text() если данные не в JSON
  })
  .then(data => {
    console.log('Полученные данные:', data);
    // Здесь вы можете работать с полученными данными
  })
  .catch(error => {
    console.error('Произошла ошибка:', error);
  });
});


Запускаем, нажимаем:

   


Теперь у вас есть данные и вы можете соорудить таблицу, графики, мини-отчеты, использовать дальше для любых своих целей. Cool

Распечатать этот элемент

Bug Переход на страницу
Автор: Lamantur - 03-18-2025, 09:32 AM - Сообщество: Динамические приложения в WINNUM Platform - Нет ответов

Всем привет!
Если мы хотим создать еще одну страницу динамического приложения, мы заранее продумываем где и когда будем ее открывать. Самый простой способ - кнопка, но возможны и более сложные многоходовочки.
Итак: создали кнопку, зашли в настройки и создаем событие: "Переход на страницу" и получаем готовое решение, которое работает в большинстве случаев:

Код:
$("#63EC5ED4-9C93-4EA2-9EDF-36573CB1EEFC").click(function(event){
    baseApplicationDesignerUtils.gotoPage('7CB5B62C-0810-4735-8936-E43D6BDA422F');
});
Интерфейс редактора динамических приложений даже подсказывает нам название новой страницы и автоматом заменяет uuid - в коде ничего не надо править.
Проблема возникает когда мы создаем несколько дистрибутивов одного и того же приложения, с теми же самыми страницами и импортируем его снова и снова, например таким образом сохраняем старые версии, чтобы опробовать разные варианты. Или просто хотим сохранить все версии для отслеживания изменений и подписываем все варианты приложений.
В этом случае окажется, что когда импортируется новая страница приложения, то создаются для страниц новые uuid.  Для главной страницы тоже.  Angry

Можем обойти эту проблему, создав более сложный переход, но универсальный, бонусом мы сможем еще и что-то передать на другую страницу прямо в адресной строке - 

Код:
$('#quality_button').click(function(event){
    thisGoToPage("", "Например");
});



/*************************************************/
function thisGoToPage( param, page_title ){
    baseSdkUtils.service.WNDynamicApplicationHelper.getPageByTitle(
        baseSdkUtils.the.appoid,
        page_title,
        function( data ){
            var item = data.getElementsByTagName('item')[0];
            var oid = item.getAttribute('elementId'); 
            window.location.href = '/Winnum/views/pages/app/dynamic/dgw.jsp?puuid=' + oid + '&appid=' + baseSdkUtils.the.appoid + param + '&mode=yes' ;
        });
}

Функция thisGoToPage принимает 2 аргумента - параметр в виде строки, при этом можно писать несколько параметров через &, например &interval=day, он появится в адресной строке, откуда его легко прочитать и использовать. 
Ну и название страницы, тоже строковая переменная.
baseSdkUtils.the.appoid - это переменная уже есть у вас с нужным значением, если вы подключали медиа библиотеку стандартным скриптом. Если нет - то посмотрите на адресную строку в вашем редакторе - oid - это он и есть.

Теперь uuid и другие временные переменные не употребляются при переходе на страницу, у значит переход сработает всегда.

Распечатать этот элемент

Information Убрать все всплывающие подсказки
Автор: Lamantur - 03-18-2025, 08:42 AM - Сообщество: Динамические приложения в WINNUM Platform - Нет ответов

Всем хорошего дня!

У меня тут всего одна строчка, но может быть очень полезна, чтобы не искать по просторам интернета:


Код:
document.querySelectorAll('[title]').forEach(element => element.removeAttribute('title'));


Если вы хотите, чтобы все подсказки исчезли на всей странице, то удалите их сразу после загрузки страницы. Так вы сразу избавитесь от всплывающих надписей, таких как "Контейнер(Пустой)" и т.д.

В редакторе лучше переименовывать эти атрибуты, они помогают пользоваться поиском на странице в редакторе:

   

По этой же причине они появятся снова после перезапуска, если их стереть из поля ввода атрибута.

А уже при запуске приложения их можно удалить все одной командой, например так:

Код:
function loadResoursesProcessor(){
    document.querySelectorAll('[title]').forEach(element => element.removeAttribute('title'));
// Остальной код
}

Распечатать этот элемент

Photo График работы оборудования
Автор: Lamantur - 02-20-2025, 01:05 PM - Сообщество: Динамические приложения в WINNUM Platform - Нет ответов

Добрый день, уважаемые коллеги!
Сегодня мы будем делать график загрузки оборудования полностью своими руками:
   

К этому графику можно добавить временную шкалу и сделать его полноценным отчетом.

У данного метода есть как преимущества, так и недостатки.
Главное преимущество - это точность расположения элементов. Кроме того самостоятельное рисование  позволяет создать абсолютно любой график с любыми временными промежутками и отрезками, варьировать размеры и цвета, а так же дает понимание того как этот график строится.

Недостаток в том, что все погрешности измерений, если они предусмотрены - видны на таком графике, т.к. он привязывается ко временным отрезкам, а не относительным величинам, можно увидеть небольшие изменения тегов, в 1 пиксель, из-за чего будут сливаться цвета.
В этой функции мы используем  " getPriorityTagDuration " для получения всех временных отрезков тегов исходя из их приоритетности. 
В результате мы получаем временные коды в миллисекундах, что дает возможность пересчитать местоположение и ширину закрашенной области. Для подсказки при наведении используем <title> в svg. Кроме того график можно обрезать и растягивать, если нужно разбить его на несколько смен, или сделать подробный график - как в стандартном приложении, для этого не нужно вызывать функцию для каждого часа. она не даст более точный результат, промежутка может быть не видно на графике, только потому, что он меньше пикселя шириной.
Ниже код функции и пример ее вызова:
appid - id приложения, можно увидеть в адресной строке
id - id html элемента, куда вставлять график
from - время от - указывается в закодированном виде, можно использовать baseSdkUtils.encode("20.02.2025 00:00:00");
to - baseSdkUtils.encode("20.02.2025 23:00:00");
product - id продукта, т.е. станка - oid - в карточке станка в адресной строке - "WNProduct"

Код:
/********************************* Процесс рисования графика **************************************/
// createBarCart( 'winnum.org.app.WNApplicationInstance:10', 'E8857FA3-F16B-423A-B043-2A353BDFE480', '19.02.2025%2019%3A30%3A00', '20.02.2025%2007%3A30%3A00', 'winnum.org.product.WNProduct:80' );

function createBarCart(appid, id, from, to, product){
var fromYMDformat = from.split("%")[0].split(".")[2] + "-" + from.split("%")[0].split(".")[1] + "-" + from.split("%")[0].split(".")[0];
var toYDMformat = to.split("%")[0].split(".")[2] + "-" + to.split("%")[0].split(".")[1] + "-" + to.split("%")[0].split(".")[0];
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
var dd = yesterday.getDate();
var mm = yesterday.getMonth() + 1;
var yyyy = yesterday.getFullYear();
if(dd<10){ dd = "0" + dd;}
if(mm<10){ mm = "0" + mm;}
var thisday = dd+"."+mm+"."+yyyy;

var today = new Date();
var td=today.getDate();
var tm = today.getMonth() + 1;
var ty = today.getFullYear();
var todaydmy = ty+"-"+tm+"-"+td;
var realtoday = td+"."+tm+"."+ty;
var yesterdaydmy = thisday.split('.')[2] + "-" + thisday.split('.')[1] + "-" + thisday.split('.')[0];

var startOfDayY = new Date(baseSdkUtils.decode(from).split(".")[2].split(" ")[0], baseSdkUtils.decode(from).split(".")[1] - 1, baseSdkUtils.decode(from).split(".")[0], baseSdkUtils.decode(from).split(" ")[1].split(":")[0], baseSdkUtils.decode(from).split(" ")[1].split(":")[1], baseSdkUtils.decode(from).split(" ")[1].split(":")[2]);
var startOfDayMs = Number(startOfDayY.getTime());

var endtOfDayY = new Date(baseSdkUtils.decode(to));
var endOfDayMs = endtOfDayY.getTime();
var arrayForDraw = [];
var tagColorSet = {};

baseSdkUtils.service.WNApplicationHelper.getTag(appid,function(data){
    for (var i = 0; i < data.getElementsByTagName('item').length; i++){
       var item = data.getElementsByTagName('item')[i];
       var tagDescription = baseSdkUtils.decode(item.getAttribute('tagName'));
       var tagColor = baseSdkUtils.decode(item.getAttribute('tagColor'));
       tagColorSet[tagDescription] = tagColor;
    }

    baseSdkUtils.service.WNApplicationTagHelper.getPriorityTagDuration(appid, product, fromYMDformat, toYDMformat, function(data){
        if ( baseSdkUtils.isSuccess(data) ){
            var str = '<svg viewBox="0 0 100 2">';
            for (var i = 0; i < data.getElementsByTagName('item').length; i++){
                var item = data.getElementsByTagName('item')[i];
                var timems = item.getAttribute('timeData');
                var tagName = baseSdkUtils.decode(item.getAttribute('TAG'));
                var startTime = baseSdkUtils.decode(item.getAttribute('START'));
                var endTime = baseSdkUtils.decode(item.getAttribute('END'));
                var now = new Date();
                var nowMs = now.getTime();
                            var x1 = (Number(timems.split(',')[0]) - startOfDayMs)/864000;
                            if(x1 < 0) x1 = 0;
                           
                            var x2 = (Number(timems.split(',')[1]) - startOfDayMs)/864000;
                            if( Number( timems.split(',')[1] ) >=  nowMs){
                                x2 = (nowMs - startOfDayMs)/864000;
                               
                            }
                            var start = baseSdkUtils.decode(item.getAttribute('START'));
                            var end = baseSdkUtils.decode(item.getAttribute('END'));
                            var serial = baseSdkUtils.decode(item.getAttribute('SERIAL_NUMBER'));
                            var duration = baseSdkUtils.decode(item.getAttribute('DURATION'));
                           
                            var colorTag = '#FFFFFF';
                           
                            for(var key in tagColorSet){
                               
                                if(tagName == key){
                               
                                    colorTag = tagColorSet[key];
                                }
                               
                            }
                           
                           if(x2 >= 0){
                                        str = str + '<polyline points="'+ x1 +',100 '+ x2 +',100 '+ x2 +',0 '+ x1 +',0" fill="'+ colorTag +'"><title>' + tagName +" "+ startTime.split(" ")[1].split(".")[0] +" - "+ endTime.split(" ")[1].split(".")[0] + '</title></polyline>';
                                }
            }
           
            str = str + "</svg>"
           
            document.getElementById(id).innerHTML = str;

           
        }
    });
});

}


Структура <SVG> дает возможность построить векторное изображение, а это значит, что вы можете увеличивать его, приближать или растянуть, качество при этом не упадет.
Вы можете задать свои цвета вместо конструкции  ColorTag, назначив их явно, тогда не придется искать их среди возможных тегов этого приложения, но они будут статичны.
График можно обновлять и сделать его онлайн. Cool
Huh В самом начале функции определяется множество переменных, это связано с необходимостью получения сегодняшнего и вчерашнего дня, чтобы можно было создавать график за текущую и предшествующую смены. Если вам не нужны привязки к текущим моментам, вы можете пользоваться  getPriorityTagDuration сразу, при том время в ней - начала и конца указывать с точностью до суток. Да такой график можно построить за 3 суток сразу. Но помните, что это не длительные отчеты - чем больше данных - тем дольше ждать ответа, а значит между вызовами нужны паузы.

Распечатать этот элемент

Lightbulb Выбор объекта
Автор: Lamantur - 01-21-2025, 12:23 PM - Сообщество: Динамические приложения в WINNUM Platform - Нет ответов

Как сделать полноценное меню с выбором объекта?

.bmp   Точечный рисунок.bmp (Размер: 2.01 KB / Загрузок: 35)

Готовое решение:
1. Скачать файл objectSelector.html в медиабиблиотеку приложения
2. Поместить файл html в контейнер как обычно:  $('#id').load(baseSdkUtils.the.media_url + '/ objectSelector.html');
3. Выбранные объекты доступны в элементе: document.getElementById('objectSelectorResult').value

----------------
Теперь подробнее:
objectSelector.html

Код:
<div class="col-lg-12 col-md-12 col-sm-8 col-xs-8 input-group wn-float-left">
<span tabindex="-1" style="cursor:help; min-width: 35px; max-width: 35px;" class="input-group-addon" id="basic-addon__object_selector_" data-trigger="focus" data-container="body" data-toggle="popover" data-placement="top" title="" data-content="Список выбранных объектов, которые связаны с приложением" data-original-title="Список объектов">
<span class="glyphicon glyphicon-plane"></span>
</span>
<input id="objectSelectorResult" type="hidden" value="">
<input winnum-field-id="_object_selector_" id="selectedObjects" winnum-field-type="text_reference" type="text" class="form-control" aria-describedby="basic-addon__object_selector_" placeholder="" winnum-field-required="true" style="padding: 6px 12px; cursor: text !important;" disabled="" title="">
<span alt="Выбрать" title="Выбрать" onclick="javascript:
baseUtils.openWindow(
'/' + baseUtils.getValue('appId') + '/views/objects/select/appInstanceProductSelector.jsp?appid='+ window.location.href.split('appid=')[1] + '&amp;callerId=callBackFunction',
'',true,500,500
);
" style="cursor:pointer;" class="input-group-addon btn btn-success">
<span class="glyphicon glyphicon-filter" aria-hidden="true"></span>
</span>
<span alt="Отменить выбор" title="Отменить выбор" onclick="javascript:
        console.log(document.getElementById('objectSelectorResult').value);
        document.getElementById('selectedObjects').value='';
        document.getElementById('selectedObjects').setAttribute('title', '');
        document.getElementById('objectSelectorResult').value='';
        document.getElementById('selectedObjects_removeBtn').setAttribute('style','cursor:pointer; display:none;');
      " id="selectedObjects_removeBtn" style="cursor:pointer; display:none;" class="input-group-addon btn btn-danger">
    <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</span>
<span style="cursor:help;" tabindex="-1" class="input-group-addon" id="basic-addon__object_selector_" data-trigger="focus" data-container="body" data-toggle="popover" data-placement="top" title="" data-content="Данное поле является обязательным для заполнения." data-original-title="Внимание - обязательное поле">
*
</span>
<script>
function callBackFunction(oids, count){
    if ( oids && count ){
        if ( count < 0 ){
            oids = '';
            count = 0;
        }
        document.getElementById('objectSelectorResult').value=oids;
        if ( count > 0 ){
            document.getElementById('selectedObjects').value = baseUtils.nls('Selected ') + count + baseUtils.nls(' objects');
        }else{
            document.getElementById('selectedObjects').value= baseUtils.nls('No objects selected');
        }
        document.getElementById('selectedObjects').setAttribute('title', document.getElementById('selectedObjects').value);
        document.getElementById('selectedObjects_removeBtn').setAttribute('style','cursor:pointer;');
    }
}
</script>
</div>


Скрипт в самом контейнере, можно прописать вызов html там же где и manifest.js, либо создать отдельный скрипт, уже без загрузки js:

Код:
$(window).ready(function(){
    loadResources();
});
function loadResources(){
    var oid = '';
    var appid = '';
    baseSdkUtils.the = {};
    baseSdkUtils.service.WNFactory.getPersistable(baseApplicationDesignerUtils.getAppid(), function(data){
        if ( !baseSdkUtils.isSuccess(data) ){
            alert('Error: ' + baseSdkUtils.decode(data.innerHTML));
            console.warn(baseSdkUtils.decode(data.innerHTML));
            return;
        }
        /******* Случай, когда код выполняется в приложении ******/
        if ( data.getElementsByTagName('item').length > 0 ){
            oid = data.getElementsByTagName('item')[0].getAttribute('ApplicationInfo__classNameA15');
            oid += ':';
            appid = data.getElementsByTagName('item')[0].getAttribute('ApplicationInfo__idA15');
            oid += appid;
        }
        /****** Случай, когда код выполняется в редакторе ********/
        else{
            oid = baseApplicationDesignerUtils.oid;
            appid = oid.split(':')[1];
        }
        /****** Создание структуры для хранения атрибутов ********/
            baseSdkUtils.the.appoid = baseApplicationDesignerUtils.getAppid();
            baseSdkUtils.the.appid = appid;
            baseSdkUtils.the.oid = oid;
            baseSdkUtils.the.media_url = '/' + baseSdkUtils.appId + '/resources/themes/current/images/app/ui/designer/media/' + appid + '/';       
        /**** Загрузка контента html в контейнер  myid **********/
            $('#myid').load(baseSdkUtils.the.media_url + '/ objectSelector.html');
        /**** Загрузка js ****************************************/
            jQuery.ajax({
                url: baseSdkUtils.the.media_url + 'manifest.js',
                dataType: 'script',
                success: function(){ loadResourcesProcessor(); },
                async: true
            });
    });
}
   



Файлы вложений
.html   objectSelector.html (Размер: 3.31 KB / Загрузок: 4)
Распечатать этот элемент

Wink Монополия
Автор: wnadmin - 12-05-2024, 12:21 PM - Сообщество: Динамические приложения в WINNUM Platform - Ответы (1)

Пример вывода карточки оборудования (карточки монополии), можно выводить одну или сколько угодно карточек за раз.

Кидаем пустой контейнер и в поле данные указываем:

Код:
<div id="card1"></div>

В поле скрипт указываем:

Код:
var pid = '2';
var appid = '17';
setTimeout(function(){
    baseUtils.injectIntoPage('/Winnum/views/pages/app/agw.jsp?rpc=winnum.views.pages.app.cnc.dashboard.HomePage&men=printSingleProduct&appid=' + appid + '&pid=' + pid + '&show_product_img=1&view=0','card1',true);
}, 1000);

,где pid - идентификатор оборудования, appid - идентификатор приложения

В результате получаем:

   

И таким же образом можно добавить столько карточек на одну страницу, сколько хочется  Heart

Распечатать этот элемент