Другое

Оптимизация setThemingColor для больших моделей SVF2 в Forge Viewer

Узнайте эффективные стратегии применения setThemingColor к большим моделям SVF2 в Forge Viewer, когда элементы идентифицируются по свойствам, а не по ID объектов. Изучите методы оптимизации для тематической раскраски на основе свойств.

Какой самый быстрый метод применения setThemingColor к элементам в большом модели svf2 Forge Viewer, когда элементы идентифицируются по конкретному свойству, а не по идентификаторам объектов?

Я работаю с очень большой моделью svf2 в Forge Viewer и мне нужно раскрашивать элементы на основе их статуса. Проблема в том, что объектное дерево занимает значительное время на загрузку, что делает процесс раскрашивания неэффективным.

Ключевые моменты:

  • Я идентифицирую элементы по конкретному свойству, а не по их идентификаторам объектов
  • Я не знаю идентификаторы объектов заранее
  • Модель слишком большая для эффективной работы с конечной точкой FetchAllProperties
  • Загрузка SQLite не является вариантом, так как она работает только с идентификаторами объектов svf1

Какие стратегии оптимизации или альтернативные подходы я могу использовать для эффективного применения тематических цветов к элементам в больших моделях svf2 без зависимости от идентификаторов объектов?

Эффективное применение setThemingColor к элементам в больших моделях SVF2 Forge Viewer при идентификации элементов по свойствам, а не по ID объектов

Чтобы эффективно применять setThemingColor к элементам в больших моделях SVF2 Forge Viewer, когда элементы идентифицируются по свойствам, а не по ID объектов, необходимо реализовать многоуровневую стратегию оптимизации, которая минимизирует загрузку данных и максимизирует целевое оформление. Ключ к успеху — использовать прогрессивные возможности загрузки Forge Viewer и реализовать интеллектуальные методы фильтрации на основе свойств.

Содержание

Понимание проблемы с большими моделями SVF2

Большие модели SVF2 представляют уникальные проблемы для оформления на основе свойств. В отличие от небольших моделей, где можно загрузить все свойства заранее, для больших моделей требуется более сложный подход для предотвращения узких мест производительности. API Model Derivative преобразует проекты в формат SVF2, поддерживающий прогрессивную загрузку, но для эффективной работы эта оптимизация требует тщательной реализации.

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

  1. Загрузить все ID объектов и свойства заранее (невозможно для больших моделей)
  2. Использовать SQLite для прямых запросов свойств (работает только с SVF1)
  3. Обходить всю дерево объектов (неприемлемо медленно)

Вместо этого нужны стратегии, которые работают с возможностями прогрессивной загрузки архитектуры SVF2, минимизируя передачу данных.

Стратегия прогрессивной загрузки свойств

Реализуйте пошаговый подход к загрузке свойств, фокусируясь только на элементах, которым требуется оформление:

javascript
// Прогрессивная загрузка свойств
async function loadPropertiesInBatches(model, propertyNames, batchSize = 1000) {
    const progress = await model.getData().progress;
    const totalElements = progress.derivatives ? progress.derivatives.length : 0;
    const batches = Math.ceil(totalElements / batchSize);
    
    const propertyPromises = [];
    
    for (let i = 0; i < batches; i++) {
        const startIndex = i * batchSize;
        const endIndex = Math.min((i + 1) * batchSize, totalElements);
        
        const batchPromise = model.getBulkProperties2({
            elementIds: [], // Пустой массив для всех элементов
            propertyNames: propertyNames,
            startIndex: startIndex,
            count: endIndex - startIndex
        });
        
        propertyPromises.push(batchPromise);
    }
    
    return Promise.all(propertyPromises);
}

Этот подход избегает загрузки всего дерева объектов сразу и вместо этого обрабатывает свойства управляемыми порциями. Forge Viewer SDK поддерживает этот шаблон прогрессивной загрузки через свои методы доступа к данным.

Оптимизированные методы идентификации свойств

Когда вам нужно идентифицировать элементы по конкретным свойствам, а не по ID объектов, рассмотрите эти оптимизированные подходы:

1. Фильтрация на основе свойств с интеллектуальными запросами

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

javascript
// Интеллектуальная фильтрация свойств
async function findElementsByProperty(model, propertyName, propertyValue) {
    const properties = await model.getProperties(['dbid'], [propertyName]);
    
    const matchingDbIds = properties.filter(prop => 
        prop.displayValue === propertyValue || 
        prop.propertyDb === propertyValue
    ).map(prop => prop.dbId);
    
    return matchingDbIds;
}

2. Стратегия кеширования для повторяющихся свойств

Реализуйте механизм кеширования для свойств, к которым обращаются часто:

javascript
// Система кеширования свойств
class PropertyCache {
    constructor() {
        this.cache = new Map();
        this.lastUpdate = 0;
    }
    
    async getProperties(model, dbId, propertyName) {
        const cacheKey = `${dbId}_${propertyName}`;
        
        if (this.cache.has(cacheKey)) {
            return this.cache.get(cacheKey);
        }
        
        const properties = await model.getProperties(dbId, [propertyName]);
        this.cache.set(cacheKey, properties);
        return properties;
    }
    
    clearCache() {
        this.cache.clear();
    }
}

3. Иерархический обход свойств

Для моделей с логическими иерархиями обходите иерархию вместо всего дерева объектов:

javascript
// Иерархический доступ к свойствам
async function traverseByHierarchy(model, rootDbId, propertyFilter) {
    const stack = [rootDbId];
    const results = [];
    
    while (stack.length > 0) {
        const currentDbId = stack.pop();
        const properties = await model.getProperties(currentDbId, ['Status', 'Category']);
        
        if (propertyFilter(properties)) {
            results.push(currentDbId);
        }
        
        // Добавление дочерних элементов в стек
        const children = await model.getInstanceTree(currentDbId).getChildren();
        stack.push(...children.map(child => child.dbId));
    }
    
    return results;
}

Альтернативные методы доступа к данным

1. Использование точек расширения данных

Используйте возможности расширения данных Forge Viewer для пользовательского доступа к свойствам:

javascript
// Пользовательское расширение данных для эффективного доступа к свойствам
class OptimizedPropertyProvider {
    constructor(viewer) {
        this.viewer = viewer;
        this.model = viewer.model;
        this.propertyCache = new Map();
    }
    
    async getOptimizedProperties(propertyNames) {
        const extension = this.viewer.getExtension('Autodesk.AdvancedPropertyPanel');
        
        if (extension) {
            return extension.getOptimizedProperties(propertyNames);
        }
        
        // Откат к стандартному методу
        return this.model.getData().properties;
    }
}

2. Индексирование свойств, специфичных для модели

Создайте пользовательские индексы на основе структуры вашей модели и часто используемых свойств:

javascript
// Пользовательское индексирование свойств
class PropertyIndex {
    constructor() {
        this.index = new Map();
        this.propertyNames = new Set();
    }
    
    async buildIndex(model, propertyNames) {
        this.propertyNames = new Set(propertyNames);
        
        const allProperties = await model.getBulkProperties2({
            elementIds: [],
            propertyNames: propertyNames,
            startIndex: 0,
            count: 1000 // Регулируйте в зависимости от производительности
        });
        
        allProperties.forEach(prop => {
            const key = `${prop.propertyName}_${prop.propertyValue}`;
            if (!this.index.has(key)) {
                this.index.set(key, []);
            }
            this.index.get(key).push(prop.dbId);
        });
    }
    
    getElementsByProperty(propertyName, propertyValue) {
        const key = `${propertyName}_${propertyValue}`;
        return this.index.get(key) || [];
    }
}

3. Web Worker для обработки свойств

Выгрузите обработку свойств в Web Workers, чтобы предотвратить блокировку пользовательского интерфейса:

javascript
// Web Worker для обработки свойств
const workerCode = `
    self.onmessage = function(e) {
        const { properties, filter } = e.data;
        const filtered = properties.filter(filter);
        self.postMessage(filtered);
    };
`;

// Использование в основном потоке
async function processPropertiesInWorker(properties, filter) {
    const blob = new Blob([workerCode], { type: 'application/javascript' });
    const worker = new Worker(URL.createObjectURL(blob));
    
    return new Promise((resolve) => {
        worker.onmessage = (e) => {
            resolve(e.data);
            worker.terminate();
        };
        
        worker.postMessage({ properties, filter });
    });
}

Лучшие практики оптимизации производительности

1. Управление памятью

Реализуйте правильное управление памятью для больших моделей:

javascript
// Утилиты оптимизации памяти
class MemoryManager {
    constructor(maxMemoryUsage = 100 * 1024 * 1024) { // лимит 100 МБ
        this.maxMemoryUsage = maxMemoryUsage;
        this.currentUsage = 0;
        this.propertyCache = new LRUCache(1000); // Ограничение размера кеша
    }
    
    addToCache(key, value) {
        const size = this.estimateSize(value);
        if (this.currentUsage + size > this.maxMemoryUsage) {
            this.clearOldestEntries(size);
        }
        this.propertyCache.set(key, value);
        this.currentUsage += size;
    }
    
    clearOldestEntries(requiredSize) {
        while (this.currentUsage > this.maxMemoryUsage - requiredSize && !this.propertyCache.isEmpty()) {
            const oldest = this.propertyCache.getOldest();
            this.currentUsage -= this.estimateSize(oldest.value);
            this.propertyCache.deleteOldest();
        }
    }
    
    estimateSize(obj) {
        return JSON.stringify(obj).length * 2; // Приблизительная оценка
    }
}

2. Пакетная обработка с ограничением скорости

Реализуйте пакетную обработку с ограничением скорости:

javascript
// Ограниченная пакетная обработка
class BatchProcessor {
    constructor(batchSize = 50, delay = 100) {
        this.batchSize = batchSize;
        this.delay = delay;
        this.queue = [];
    }
    
    async process(model, items, processor) {
        const results = [];
        
        for (let i = 0; i < items.length; i += this.batchSize) {
            const batch = items.slice(i, i + this.batchSize);
            const batchResults = await processor(model, batch);
            results.push(...batchResults);
            
            if (i + this.batchSize < items.length) {
                await new Promise(resolve => setTimeout(resolve, this.delay));
            }
        }
        
        return results;
    }
}

3. Ленивая загрузка данных свойств

Реализуйте ленивую загрузку для свойств, к которым обращаются редко:

javascript
// Ленивая загрузка свойств
class LazyPropertyLoader {
    constructor(model) {
        this.model = model;
        this.loadedProperties = new Set();
        this.propertyPromises = new Map();
    }
    
    async getProperty(dbId, propertyName) {
        const cacheKey = `${dbId}_${propertyName}`;
        
        if (this.loadedProperties.has(cacheKey)) {
            return this.model.getProperties(dbId, [propertyName]);
        }
        
        if (this.propertyPromises.has(cacheKey)) {
            return this.propertyPromises.get(cacheKey);
        }
        
        const promise = this.model.getProperties(dbId, [propertyName])
            .finally(() => {
                this.loadedProperties.add(cacheKey);
                this.propertyPromises.delete(cacheKey);
            });
        
        this.propertyPromises.set(cacheKey, promise);
        return promise;
    }
}

Примеры реализации

Полный оптимизированный решения для оформления

Вот комплексная реализация, объединяющая несколько стратегий оптимизации:

javascript
class OptimizedThemingManager {
    constructor(viewer) {
        this.viewer = viewer;
        this.model = viewer.model;
        this.propertyCache = new PropertyCache();
        this.memoryManager = new MemoryManager();
        this.batchProcessor = new BatchProcessor(100, 50);
        this.lazyLoader = new LazyPropertyLoader(this.model);
    }
    
    async applyThemingByProperty(propertyName, propertyValue, color) {
        try {
            // Шаг 1: Поиск ID элементов с использованием оптимизированного поиска свойств
            const elementIds = await this.findElementsByProperty(propertyName, propertyValue);
            
            if (elementIds.length === 0) {
                console.warn(`Не найдено элементов со свойством ${propertyName} = ${propertyValue}`);
                return;
            }
            
            // Шаг 2: Применение оформления пакетами
            await this.batchProcessor.process(this.model, elementIds, async (model, batch) => {
                await this.applyThemingToBatch(batch, color);
                return batch.length;
            });
            
            console.log(`Оформление применено к ${elementIds.length} элементам`);
        } catch (error) {
            console.error('Ошибка применения оформления:', error);
        }
    }
    
    async findElementsByProperty(propertyName, propertyValue) {
        // Проверка кеша сначала
        const cacheKey = `${propertyName}_${propertyValue}`;
        if (this.propertyCache.has(cacheKey)) {
            return this.propertyCache.get(cacheKey);
        }
        
        // Использование иерархического обхода для больших моделей
        const elementIds = await this.traverseByProperty(propertyName, propertyValue);
        
        // Кеширование результата
        this.propertyCache.set(cacheKey, elementIds);
        return elementIds;
    }
    
    async traverseByProperty(propertyName, propertyValue) {
        const rootId = (await this.model.getRootId()).dbId;
        return this.lazyLoader.traverseHierarchy(rootId, async (dbId) => {
            const properties = await this.lazyLoader.getProperty(dbId, propertyName);
            return properties.some(prop => 
                prop.displayValue === propertyValue || 
                prop.propertyValue === propertyValue
            );
        });
    }
    
    async applyThemingToBatch(elementIds, color) {
        const fragIds = elementIds.map(id => this.model.getFragmentIdFromDbId(id));
        
        // Использование setThemingColor с ID фрагментов для лучшей производительности
        this.viewer.impl.setThemingColor(fragIds, color, 0);
        
        // Принудительное немедленное обновление
        this.viewer.impl.invalidate(true);
    }
}

// Пример использования
async function setupOptimizedTheming(viewer) {
    const themingManager = new OptimizedThemingManager(viewer);
    
    // Применение оформления при загрузке модели
    viewer.addEventListener('modelLoaded', async () => {
        await themingManager.applyThemingByProperty('Status', 'Critical', new THREE.Color(0xff0000));
    });
}

Мониторинг производительности

Реализуйте мониторинг производительности для отслеживания эффективности оптимизации:

javascript
class PerformanceMonitor {
    constructor() {
        this.metrics = {
            propertyLoadTime: 0,
            themingApplyTime: 0,
            memoryUsage: 0,
            cacheHits: 0,
            cacheMisses: 0
        };
    }
    
    async measurePropertyLoad(fn, ...args) {
        const start = performance.now();
        const result = await fn(...args);
        const duration = performance.now() - start;
        
        this.metrics.propertyLoadTime += duration;
        return result;
    }
    
    measureThemingApply(fn, ...args) {
        const start = performance.now();
        const result = fn(...args);
        const duration = performance.now() - start;
        
        this.metrics.themingApplyTime += duration;
        return result;
    }
    
    getMetrics() {
        return { ...this.metrics };
    }
    
    resetMetrics() {
        Object.keys(this.metrics).forEach(key => {
            this.metrics[key] = 0;
        });
    }
}

Заключение

Оптимизация применения setThemingColor для больших моделей SVF2 требует многогранного подхода, который решает уникальные проблемы идентификации элементов на основе свойств. Ключевые стратегии включают:

  1. Прогрессивная загрузка: Обрабатывайте свойства управляемыми порциями, а не загружайте все сразу
  2. Интеллектуальное кеширование: Реализуйте интеллектуальное кеширование для часто используемых свойств
  3. Иерархический обход: Используйте иерархии моделей вместо обхода плоского дерева объектов
  4. Пакетная обработка: Применяйте операции оформления оптимизированными пакетами
  5. Управление памятью: Реализуйте правильные механизмы управления памятью для больших наборов данных
  6. Web Workers: Выгружайте интенсивную обработку в фоновые потоки для предотвращения блокировки UI

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

Всегда отслеживайте метрики производительности и корректируйте стратегии в зависимости от конкретных характеристик ваших моделей. Autodesk Forge Viewer SDK предоставляет мощные инструменты, но их эффективность зависит от того, насколько хорошо вы реализуете эти шаблоны оптимизации.

Источники

  1. Model Derivative API | Autodesk Platform Services
  2. Viewer SDK | Autodesk Platform Services
  3. Working with 2D and 3D Scenes and Geometry in Forge Viewer
  4. Show/Hide Textures of Object in Forge Viewer
  5. Embed APS Viewer inside Power BI report
Авторы
Проверено модерацией
Модерация