fix(visualizador3d): integracao fiel do driver SteelXR web-ifc@0.0.57 e estilizacao grafica vibrante
This commit is contained in:
+1
-1
@@ -61,7 +61,7 @@
|
|||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"three": "^0.186.0",
|
"three": "^0.186.0",
|
||||||
"three-stdlib": "^2.36.1",
|
"three-stdlib": "^2.36.1",
|
||||||
"web-ifc": "^0.0.74",
|
"web-ifc": "^0.0.57",
|
||||||
"xlsx": "^0.18.5",
|
"xlsx": "^0.18.5",
|
||||||
"zod": "^3.25.76"
|
"zod": "^3.25.76"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { OrbitControls } from 'three-stdlib';
|
import { OrbitControls } from 'three-stdlib';
|
||||||
import { PieceInfo, LoadedIFCResult } from '@/lib/ifc/ifcLoaderService';
|
import { PieceInfo, LoadedIFCResult, getColorForMaterialName } from '@/lib/ifc/ifcLoaderService';
|
||||||
import { ViewCube } from './ViewCube';
|
import { ViewCube } from './ViewCube';
|
||||||
import { Viewer3DToolbar } from './Viewer3DToolbar';
|
import { Viewer3DToolbar } from './Viewer3DToolbar';
|
||||||
import { PieceHoverTooltip } from './PieceHoverTooltip';
|
import { PieceHoverTooltip } from './PieceHoverTooltip';
|
||||||
@@ -99,12 +99,15 @@ export const ModelViewer3D: React.FC<ModelViewer3DProps> = ({
|
|||||||
|
|
||||||
activeCameraRef.current = perspCamera;
|
activeCameraRef.current = perspCamera;
|
||||||
|
|
||||||
// 3. Renderer
|
// 3. Renderer com padronizacao grafica idêntica ao SteelXR
|
||||||
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: 'high-performance' });
|
||||||
renderer.setSize(width, height);
|
renderer.setSize(width, height);
|
||||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||||
renderer.shadowMap.enabled = true;
|
renderer.shadowMap.enabled = true;
|
||||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
renderer.shadowMap.type = THREE.PCFShadowMap;
|
||||||
|
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||||||
|
renderer.toneMappingExposure = 1.05;
|
||||||
|
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||||
rendererRef.current = renderer;
|
rendererRef.current = renderer;
|
||||||
|
|
||||||
containerRef.current.innerHTML = '';
|
containerRef.current.innerHTML = '';
|
||||||
@@ -279,23 +282,35 @@ export const ModelViewer3D: React.FC<ModelViewer3DProps> = ({
|
|||||||
side: THREE.DoubleSide,
|
side: THREE.DoubleSide,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Não apontada / Pendente: Cinza claro padrão industrial
|
// Não apontada / Pendente: Cinza claro padrão industrial (#a1a1aa)
|
||||||
mesh.material = new THREE.MeshStandardMaterial({
|
mesh.material = new THREE.MeshStandardMaterial({
|
||||||
color: 0x94a3b8,
|
color: new THREE.Color(0xa1a1aa),
|
||||||
metalness: 0.25,
|
metalness: 0.2,
|
||||||
roughness: 0.65,
|
roughness: 0.7,
|
||||||
transparent: opacity < 100,
|
transparent: opacity < 100,
|
||||||
opacity: opacity / 100,
|
opacity: opacity / 100,
|
||||||
side: THREE.DoubleSide,
|
side: THREE.DoubleSide,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Color Mode "Descrição" (Palette by Profile/Section)
|
// Color Mode "Descrição": Cores ricas por perfil e material idênticas ao SteelXR
|
||||||
const colorKey = mesh.userData.colorKey;
|
const parentData = mesh.parent?.userData || {};
|
||||||
const cachedMat = modelData.materialsByColor.get(colorKey);
|
const label =
|
||||||
if (cachedMat) {
|
mesh.userData.section ||
|
||||||
mesh.material = cachedMat;
|
parentData.section ||
|
||||||
}
|
mesh.userData.materialName ||
|
||||||
|
parentData.materialName ||
|
||||||
|
mesh.userData.pieceMark ||
|
||||||
|
'PADRAO';
|
||||||
|
const hsl = getColorForMaterialName(label);
|
||||||
|
mesh.material = new THREE.MeshStandardMaterial({
|
||||||
|
color: new THREE.Color(hsl),
|
||||||
|
metalness: 0.25,
|
||||||
|
roughness: 0.65,
|
||||||
|
transparent: opacity < 100,
|
||||||
|
opacity: opacity / 100,
|
||||||
|
side: THREE.DoubleSide,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply Opacity & Wireframe
|
// Apply Opacity & Wireframe
|
||||||
|
|||||||
+166
-101
@@ -50,21 +50,20 @@ export interface LoadedIFCResult {
|
|||||||
totalMeshes: number;
|
totalMeshes: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paleta por Descrição/Seção (Modo "Descrição")
|
/**
|
||||||
const DESCRIPTION_PALETTE = [
|
* Função idêntica ao SteelXR para gerar cores vibrantes e distintas por descrição / perfil / material.
|
||||||
'#3b82f6', // azul
|
*/
|
||||||
'#ec4899', // rosa
|
export function getColorForMaterialName(name: string): string {
|
||||||
'#10b981', // esmeralda
|
if (!name) return '#a1a1aa';
|
||||||
'#f59e0b', // ambar
|
let hash = 0;
|
||||||
'#8b5cf6', // roxo
|
for (let i = 0; i < name.length; i++) {
|
||||||
'#06b6d4', // ciano
|
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
'#ef4444', // vermelho
|
}
|
||||||
'#84cc16', // lima
|
const h = Math.abs(hash) % 360;
|
||||||
'#6366f1', // indigo
|
const s = 65 + (Math.abs(hash >> 8) % 15);
|
||||||
'#14b8a6', // teal
|
const l = 45 + (Math.abs(hash >> 16) % 10);
|
||||||
'#f97316', // laranja
|
return `hsl(${h}, ${s}%, ${l}%)`;
|
||||||
'#eab308', // amarelo
|
}
|
||||||
];
|
|
||||||
|
|
||||||
// Helper: Valida se um valor extraído é uma marca útil real
|
// Helper: Valida se um valor extraído é uma marca útil real
|
||||||
export function isValidMark(val: any): boolean {
|
export function isValidMark(val: any): boolean {
|
||||||
@@ -111,14 +110,12 @@ export function getCleanMarkValue(rawMark: string): string {
|
|||||||
if (!rawMark) return '';
|
if (!rawMark) return '';
|
||||||
let cleaned = rawMark.trim();
|
let cleaned = rawMark.trim();
|
||||||
|
|
||||||
// Se tiver hífen, o último segmento costuma ser a marca unitária da peça (ex: OF-FASE-MARCA -> MARCA)
|
|
||||||
if (cleaned.includes('-')) {
|
if (cleaned.includes('-')) {
|
||||||
const parts = cleaned.split('-');
|
const parts = cleaned.split('-');
|
||||||
const last = parts[parts.length - 1].trim();
|
const last = parts[parts.length - 1].trim();
|
||||||
if (last && isValidMark(last)) return last;
|
if (last && isValidMark(last)) return last;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se contiver prefixo tipo "Pos: 4" ou "Mark: 4"
|
|
||||||
const prefixMatch = cleaned.match(/(?:Mark|Pos|Marca|Posicao)[:\s=]*([^\s,;]+)/i);
|
const prefixMatch = cleaned.match(/(?:Mark|Pos|Marca|Posicao)[:\s=]*([^\s,;]+)/i);
|
||||||
if (prefixMatch && isValidMark(prefixMatch[1])) {
|
if (prefixMatch && isValidMark(prefixMatch[1])) {
|
||||||
return prefixMatch[1].trim();
|
return prefixMatch[1].trim();
|
||||||
@@ -127,19 +124,66 @@ export function getCleanMarkValue(rawMark: string): string {
|
|||||||
return cleaned;
|
return cleaned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: Mapeamento de nome de material idêntico ao SteelXR
|
||||||
|
function getMaterialName(ifcApi: WebIFC.IfcAPI, modelID: number, matRef: any): string | null {
|
||||||
|
if (!matRef) return null;
|
||||||
|
const matId = matRef.value;
|
||||||
|
if (!matId) return null;
|
||||||
|
try {
|
||||||
|
const matLine = ifcApi.GetLine(modelID, matId);
|
||||||
|
if (!matLine) return null;
|
||||||
|
|
||||||
|
if (matLine.Name && matLine.Name.value) {
|
||||||
|
return matLine.Name.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matLine.Materials) {
|
||||||
|
for (const mRef of matLine.Materials) {
|
||||||
|
const name = getMaterialName(ifcApi, modelID, mRef);
|
||||||
|
if (name) return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matLine.MaterialConstituents) {
|
||||||
|
for (const mcRef of matLine.MaterialConstituents) {
|
||||||
|
const mcLine = ifcApi.GetLine(modelID, mcRef.value);
|
||||||
|
if (mcLine && mcLine.Material) {
|
||||||
|
const name = getMaterialName(ifcApi, modelID, mcLine.Material);
|
||||||
|
if (name) return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matLine.MaterialProfileSet) {
|
||||||
|
return getMaterialName(ifcApi, modelID, matLine.MaterialProfileSet);
|
||||||
|
}
|
||||||
|
if (matLine.MaterialProfiles) {
|
||||||
|
for (const mpRef of matLine.MaterialProfiles) {
|
||||||
|
const mpLine = ifcApi.GetLine(modelID, mpRef.value);
|
||||||
|
if (mpLine && mpLine.Material) {
|
||||||
|
const name = getMaterialName(ifcApi, modelID, mpLine.Material);
|
||||||
|
if (name) return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadAndAuditIFC(
|
export async function loadAndAuditIFC(
|
||||||
fileOrBuffer: File | ArrayBuffer,
|
fileOrBuffer: File | ArrayBuffer,
|
||||||
onProgress?: (percent: number, step: string) => void
|
onProgress?: (percent: number, step: string) => void
|
||||||
): Promise<LoadedIFCResult> {
|
): Promise<LoadedIFCResult> {
|
||||||
const ifcApi = new WebIFC.IfcAPI();
|
const ifcApi = new WebIFC.IfcAPI();
|
||||||
|
|
||||||
// 1. Inicializa Web-IFC WASM com fallback
|
// 1. Inicializa Web-IFC com a versão 0.0.57 (mesma versão estável do SteelXR)
|
||||||
if (onProgress) onProgress(10, 'Carregando motor Web-IFC (WASM)...');
|
if (onProgress) onProgress(10, 'Carregando motor SteelXR IFC (WASM)...');
|
||||||
|
|
||||||
const LOCAL_WASM_PATH = '/wasm/';
|
const WASM_PATH_057 = 'https://unpkg.com/web-ifc@0.0.57/';
|
||||||
const CDN_WASM_PATH = 'https://unpkg.com/web-ifc@0.0.74/';
|
|
||||||
|
|
||||||
// Intercepta logs do console temporariamente para filtrar avisos internos da engine C++
|
// Filtra avisos internos da biblioteca C++ para manter o console limpo
|
||||||
const originalConsoleError = console.error;
|
const originalConsoleError = console.error;
|
||||||
const originalConsoleWarn = console.warn;
|
const originalConsoleWarn = console.warn;
|
||||||
console.error = (...args: any[]) => {
|
console.error = (...args: any[]) => {
|
||||||
@@ -159,17 +203,17 @@ export async function loadAndAuditIFC(
|
|||||||
|
|
||||||
let wasmInitSuccess = false;
|
let wasmInitSuccess = false;
|
||||||
try {
|
try {
|
||||||
ifcApi.SetWasmPath(LOCAL_WASM_PATH, true);
|
ifcApi.SetWasmPath(WASM_PATH_057, true);
|
||||||
await ifcApi.Init();
|
await ifcApi.Init();
|
||||||
wasmInitSuccess = true;
|
wasmInitSuccess = true;
|
||||||
} catch (localErr) {
|
} catch (cdnErr) {
|
||||||
console.warn('[IFC Loader] Falha ao carregar WASM local (/wasm/), tentando CDN...', localErr);
|
console.warn('[IFC Loader] Tentando fallback para /wasm/...', cdnErr);
|
||||||
try {
|
try {
|
||||||
ifcApi.SetWasmPath(CDN_WASM_PATH, true);
|
ifcApi.SetWasmPath('/wasm/', true);
|
||||||
await ifcApi.Init();
|
await ifcApi.Init();
|
||||||
wasmInitSuccess = true;
|
wasmInitSuccess = true;
|
||||||
} catch (cdnErr) {
|
} catch (localErr) {
|
||||||
console.error('[IFC Loader] Falha ao inicializar Web-IFC via CDN:', cdnErr);
|
console.error('[IFC Loader] Falha ao inicializar Web-IFC:', localErr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,12 +236,9 @@ export async function loadAndAuditIFC(
|
|||||||
rawHeader = decoder.decode(data.slice(0, 4096));
|
rawHeader = decoder.decode(data.slice(0, 4096));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Abertura do Modelo com suporte a coordenadas relativas
|
// 3. Abertura do Modelo idêntica ao SteelXR (OpenModel puro, compatibilidade máxima)
|
||||||
if (onProgress) onProgress(40, 'Decodificando entidades IFC...');
|
if (onProgress) onProgress(40, 'Decodificando entidades IFC...');
|
||||||
const modelID = ifcApi.OpenModel(data, {
|
const modelID = ifcApi.OpenModel(data);
|
||||||
COORDINATE_TO_ORIGIN: true,
|
|
||||||
USE_FAST_BOOLS: true
|
|
||||||
} as any);
|
|
||||||
if (modelID < 0) {
|
if (modelID < 0) {
|
||||||
throw new Error('Falha ao decodificar a estrutura do arquivo IFC.');
|
throw new Error('Falha ao decodificar a estrutura do arquivo IFC.');
|
||||||
}
|
}
|
||||||
@@ -244,7 +285,27 @@ export async function loadAndAuditIFC(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4.2 Grafo de Soldas (IFCRELCONNECTSWITHREALIZINGELEMENTS)
|
// 4.2 Mapeamento de Materiais do SteelXR (IFCRELASSOCIATESMATERIAL)
|
||||||
|
const elementMaterialMap = new Map<number, string>();
|
||||||
|
try {
|
||||||
|
const rels = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCRELASSOCIATESMATERIAL);
|
||||||
|
for (let i = 0; i < rels.size(); i++) {
|
||||||
|
const relId = rels.get(i);
|
||||||
|
const rel = ifcApi.GetLine(modelID, relId);
|
||||||
|
if (rel && rel.RelatedObjects && rel.RelatingMaterial) {
|
||||||
|
const matName = getMaterialName(ifcApi, modelID, rel.RelatingMaterial);
|
||||||
|
if (matName) {
|
||||||
|
for (const objRef of rel.RelatedObjects) {
|
||||||
|
elementMaterialMap.set(objRef.value, matName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4.3 Mapeamento de Soldas (IFCRELCONNECTSWITHREALIZINGELEMENTS)
|
||||||
const weldConnections = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCRELCONNECTSWITHREALIZINGELEMENTS);
|
const weldConnections = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCRELCONNECTSWITHREALIZINGELEMENTS);
|
||||||
const adjacencyList = new Map<number, Set<number>>();
|
const adjacencyList = new Map<number, Set<number>>();
|
||||||
|
|
||||||
@@ -269,7 +330,6 @@ export async function loadAndAuditIFC(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Componentes conectados soldados
|
|
||||||
const visited = new Set<number>();
|
const visited = new Set<number>();
|
||||||
const weldedGroupMap = new Map<number, number[]>();
|
const weldedGroupMap = new Map<number, number[]>();
|
||||||
let totalWeldedGroups = 0;
|
let totalWeldedGroups = 0;
|
||||||
@@ -299,7 +359,7 @@ export async function loadAndAuditIFC(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4.3 Indexação profunda de propriedades (IFCRELDEFINESBYPROPERTIES)
|
// 4.4 Indexação completa de propriedades (IFCRELDEFINESBYPROPERTIES) idêntica ao SteelXR
|
||||||
const elementPropertiesMap = new Map<number, Record<string, string>>();
|
const elementPropertiesMap = new Map<number, Record<string, string>>();
|
||||||
try {
|
try {
|
||||||
const relsProp = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCRELDEFINESBYPROPERTIES);
|
const relsProp = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCRELDEFINESBYPROPERTIES);
|
||||||
@@ -314,7 +374,7 @@ export async function loadAndAuditIFC(
|
|||||||
if (propDef) {
|
if (propDef) {
|
||||||
const props: Record<string, string> = {};
|
const props: Record<string, string> = {};
|
||||||
|
|
||||||
// Leitura de HasProperties (Pset_*)
|
// HasProperties (Pset_*)
|
||||||
if (propDef.HasProperties && Array.isArray(propDef.HasProperties)) {
|
if (propDef.HasProperties && Array.isArray(propDef.HasProperties)) {
|
||||||
for (const pRef of propDef.HasProperties) {
|
for (const pRef of propDef.HasProperties) {
|
||||||
const pId = pRef?.value;
|
const pId = pRef?.value;
|
||||||
@@ -338,7 +398,7 @@ export async function loadAndAuditIFC(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Leitura de Quantities (Qto_*)
|
// Quantities (Qto_*)
|
||||||
if (propDef.Quantities && Array.isArray(propDef.Quantities)) {
|
if (propDef.Quantities && Array.isArray(propDef.Quantities)) {
|
||||||
for (const qRef of propDef.Quantities) {
|
for (const qRef of propDef.Quantities) {
|
||||||
const qId = qRef?.value;
|
const qId = qRef?.value;
|
||||||
@@ -401,7 +461,6 @@ export async function loadAndAuditIFC(
|
|||||||
|
|
||||||
const descParsed = parseDescriptionFields(rawDesc);
|
const descParsed = parseDescriptionFields(rawDesc);
|
||||||
|
|
||||||
// Prioridade de propriedades que representam Marca/Posição da peça
|
|
||||||
const candidates: (string | undefined)[] = [
|
const candidates: (string | undefined)[] = [
|
||||||
props.PieceMark,
|
props.PieceMark,
|
||||||
props.PartMark,
|
props.PartMark,
|
||||||
@@ -421,7 +480,7 @@ export async function loadAndAuditIFC(
|
|||||||
props.PartNumber,
|
props.PartNumber,
|
||||||
rawTag,
|
rawTag,
|
||||||
props.Tag,
|
props.Tag,
|
||||||
props.Reference, // Validado por isValidMark
|
props.Reference,
|
||||||
rawObjType,
|
rawObjType,
|
||||||
rawName
|
rawName
|
||||||
];
|
];
|
||||||
@@ -434,7 +493,6 @@ export async function loadAndAuditIFC(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se não encontrou no próprio elemento, tenta herdar da montagem pai
|
|
||||||
if (!chosenMark && childToAssembly.has(expressID)) {
|
if (!chosenMark && childToAssembly.has(expressID)) {
|
||||||
const parentId = childToAssembly.get(expressID)!;
|
const parentId = childToAssembly.get(expressID)!;
|
||||||
const parentProps = elementPropertiesMap.get(parentId) || {};
|
const parentProps = elementPropertiesMap.get(parentId) || {};
|
||||||
@@ -484,8 +542,6 @@ export async function loadAndAuditIFC(
|
|||||||
const piecesByMark = new Map<string, PieceInfo[]>();
|
const piecesByMark = new Map<string, PieceInfo[]>();
|
||||||
const pieceByExpressID = new Map<number, PieceInfo>();
|
const pieceByExpressID = new Map<number, PieceInfo>();
|
||||||
const uniqueMarksSet = new Set<string>();
|
const uniqueMarksSet = new Set<string>();
|
||||||
const sectionColorMap = new Map<string, string>();
|
|
||||||
let colorIndex = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < assemblies.size(); i++) {
|
for (let i = 0; i < assemblies.size(); i++) {
|
||||||
const assID = assemblies.get(i);
|
const assID = assemblies.get(i);
|
||||||
@@ -520,57 +576,48 @@ export async function loadAndAuditIFC(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Geração de Malhas 3D com Three.js corrigindo o empacotamento de vértices/normais
|
// 5. Geração de Malhas 3D idêntica ao SteelXR
|
||||||
if (onProgress) onProgress(75, 'Gerando malhas geométricas 3D...');
|
if (onProgress) onProgress(75, 'Gerando malhas geométricas 3D...');
|
||||||
const sceneGroup = new THREE.Group();
|
const sceneGroup = new THREE.Group();
|
||||||
sceneGroup.name = 'IFC_ROOT_MODEL';
|
sceneGroup.name = 'IFC_ROOT_MODEL';
|
||||||
|
|
||||||
const defaultGrayMat = new THREE.MeshStandardMaterial({
|
const materialsCache: Map<number, THREE.MeshStandardMaterial> = new Map();
|
||||||
color: 0x94a3b8,
|
|
||||||
metalness: 0.3,
|
|
||||||
roughness: 0.6,
|
|
||||||
side: THREE.DoubleSide
|
|
||||||
});
|
|
||||||
|
|
||||||
const materialCache = new Map<string, THREE.MeshStandardMaterial>();
|
|
||||||
let totalMeshes = 0;
|
let totalMeshes = 0;
|
||||||
|
|
||||||
ifcApi.StreamAllMeshes(modelID, (flatMesh: WebIFC.FlatMesh) => {
|
ifcApi.StreamAllMeshes(modelID, (mesh: WebIFC.FlatMesh) => {
|
||||||
const expressID = flatMesh.expressID;
|
const placedGeometries = mesh.geometries;
|
||||||
const placedGeometries = flatMesh.geometries;
|
const expressID = (mesh as unknown as { expressID?: number }).expressID ?? 0;
|
||||||
|
|
||||||
const markInfo = extractBestMark(expressID);
|
const markInfo = extractBestMark(expressID);
|
||||||
const colorKey = markInfo.section || 'PADRAO';
|
const materialName = elementMaterialMap.get(expressID) ?? '';
|
||||||
|
|
||||||
if (!sectionColorMap.has(colorKey)) {
|
|
||||||
sectionColorMap.set(colorKey, DESCRIPTION_PALETTE[colorIndex % DESCRIPTION_PALETTE.length]);
|
|
||||||
colorIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hexColor = sectionColorMap.get(colorKey)!;
|
|
||||||
if (!materialCache.has(hexColor)) {
|
|
||||||
materialCache.set(
|
|
||||||
hexColor,
|
|
||||||
new THREE.MeshStandardMaterial({
|
|
||||||
color: new THREE.Color(hexColor),
|
|
||||||
metalness: 0.35,
|
|
||||||
roughness: 0.55,
|
|
||||||
side: THREE.DoubleSide
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const mat = materialCache.get(hexColor) || defaultGrayMat;
|
|
||||||
const connectedGroup = weldedGroupMap.get(expressID) || [expressID];
|
const connectedGroup = weldedGroupMap.get(expressID) || [expressID];
|
||||||
|
|
||||||
for (let g = 0; g < placedGeometries.size(); g++) {
|
const elementGroup = new THREE.Group();
|
||||||
const placedGeo = placedGeometries.get(g);
|
elementGroup.name = `ifc_${expressID}`;
|
||||||
const ifcGeo = ifcApi.GetGeometry(modelID, placedGeo.geometryExpressID);
|
elementGroup.userData = {
|
||||||
|
ifcElement: true,
|
||||||
|
ifcId: expressID,
|
||||||
|
pieceMark: markInfo.mark,
|
||||||
|
cleanMark: markInfo.cleanMark,
|
||||||
|
section: markInfo.section,
|
||||||
|
materialName,
|
||||||
|
connectedMeshIDs: connectedGroup
|
||||||
|
};
|
||||||
|
|
||||||
// Web-IFC GetVertexArray retorna [px, py, pz, nx, ny, nz, ...] (interleaved de 6 floats)
|
for (let i = 0; i < placedGeometries.size(); i++) {
|
||||||
const verts = ifcApi.GetVertexArray(ifcGeo.GetVertexData(), ifcGeo.GetVertexDataSize());
|
const placedGeometry = placedGeometries.get(i);
|
||||||
const indices = ifcApi.GetIndexArray(ifcGeo.GetIndexData(), ifcGeo.GetIndexDataSize());
|
const ifcGeometry = ifcApi.GetGeometry(modelID, placedGeometry.geometryExpressID);
|
||||||
|
|
||||||
const bufferGeo = new THREE.BufferGeometry();
|
const verts = ifcApi.GetVertexArray(
|
||||||
|
ifcGeometry.GetVertexData(),
|
||||||
|
ifcGeometry.GetVertexDataSize()
|
||||||
|
);
|
||||||
|
const indices = ifcApi.GetIndexArray(
|
||||||
|
ifcGeometry.GetIndexData(),
|
||||||
|
ifcGeometry.GetIndexDataSize()
|
||||||
|
);
|
||||||
|
|
||||||
|
const geometry = new THREE.BufferGeometry();
|
||||||
const numVerts = verts.length / 6;
|
const numVerts = verts.length / 6;
|
||||||
const positionArray = new Float32Array(numVerts * 3);
|
const positionArray = new Float32Array(numVerts * 3);
|
||||||
const normalArray = new Float32Array(numVerts * 3);
|
const normalArray = new Float32Array(numVerts * 3);
|
||||||
@@ -585,26 +632,43 @@ export async function loadAndAuditIFC(
|
|||||||
normalArray[idx * 3 + 2] = verts[j + 5];
|
normalArray[idx * 3 + 2] = verts[j + 5];
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferGeo.setAttribute('position', new THREE.BufferAttribute(positionArray, 3));
|
geometry.setAttribute('position', new THREE.BufferAttribute(positionArray, 3));
|
||||||
bufferGeo.setAttribute('normal', new THREE.BufferAttribute(normalArray, 3));
|
geometry.setAttribute('normal', new THREE.BufferAttribute(normalArray, 3));
|
||||||
bufferGeo.setIndex(new THREE.BufferAttribute(new Uint32Array(indices), 1));
|
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
|
||||||
|
|
||||||
const mesh = new THREE.Mesh(bufferGeo, mat);
|
// Cor original nativa do IFC igual ao SteelXR
|
||||||
const matrix = new THREE.Matrix4();
|
const color = placedGeometry.color;
|
||||||
matrix.fromArray(placedGeo.flatTransformation);
|
const colorKey = (color.x * 255) << 16 | (color.y * 255) << 8 | (color.z * 255);
|
||||||
mesh.applyMatrix4(matrix);
|
let material = materialsCache.get(colorKey);
|
||||||
|
if (!material) {
|
||||||
|
material = new THREE.MeshStandardMaterial({
|
||||||
|
color: new THREE.Color(color.x, color.y, color.z),
|
||||||
|
metalness: 0.25,
|
||||||
|
roughness: 0.65,
|
||||||
|
transparent: color.w < 1,
|
||||||
|
opacity: color.w,
|
||||||
|
side: THREE.DoubleSide,
|
||||||
|
});
|
||||||
|
materialsCache.set(colorKey, material);
|
||||||
|
}
|
||||||
|
|
||||||
mesh.userData = {
|
const mesh3 = new THREE.Mesh(geometry, material);
|
||||||
expressID,
|
mesh3.userData = {
|
||||||
|
ifcId: expressID,
|
||||||
pieceMark: markInfo.mark,
|
pieceMark: markInfo.mark,
|
||||||
cleanMark: markInfo.cleanMark,
|
cleanMark: markInfo.cleanMark,
|
||||||
section: markInfo.section,
|
section: markInfo.section,
|
||||||
colorKey,
|
materialName,
|
||||||
|
nativeColor: new THREE.Color(color.x, color.y, color.z),
|
||||||
connectedMeshIDs: connectedGroup
|
connectedMeshIDs: connectedGroup
|
||||||
};
|
};
|
||||||
|
|
||||||
sceneGroup.add(mesh);
|
const matrix = new THREE.Matrix4();
|
||||||
ifcGeo.delete();
|
matrix.fromArray(placedGeometry.flatTransformation);
|
||||||
|
mesh3.applyMatrix4(matrix);
|
||||||
|
|
||||||
|
elementGroup.add(mesh3);
|
||||||
|
ifcGeometry.delete();
|
||||||
totalMeshes++;
|
totalMeshes++;
|
||||||
|
|
||||||
if (!pieceByExpressID.has(expressID)) {
|
if (!pieceByExpressID.has(expressID)) {
|
||||||
@@ -616,8 +680,9 @@ export async function loadAndAuditIFC(
|
|||||||
pieceMark: markInfo.mark,
|
pieceMark: markInfo.mark,
|
||||||
cleanMark: markInfo.cleanMark,
|
cleanMark: markInfo.cleanMark,
|
||||||
section: markInfo.section,
|
section: markInfo.section,
|
||||||
|
material: materialName,
|
||||||
connectedMeshIDs: connectedGroup,
|
connectedMeshIDs: connectedGroup,
|
||||||
mesh
|
mesh: mesh3
|
||||||
};
|
};
|
||||||
pieceByExpressID.set(expressID, piece);
|
pieceByExpressID.set(expressID, piece);
|
||||||
if (markInfo.mark && markInfo.mark !== 'indefinido') {
|
if (markInfo.mark && markInfo.mark !== 'indefinido') {
|
||||||
@@ -626,14 +691,14 @@ export async function loadAndAuditIFC(
|
|||||||
if (!piecesByMark.has(markInfo.mark)) piecesByMark.set(markInfo.mark, []);
|
if (!piecesByMark.has(markInfo.mark)) piecesByMark.set(markInfo.mark, []);
|
||||||
piecesByMark.get(markInfo.mark)!.push(piece);
|
piecesByMark.get(markInfo.mark)!.push(piece);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
pieceByExpressID.get(expressID)!.mesh = mesh;
|
|
||||||
pieceByExpressID.get(expressID)!.connectedMeshIDs = connectedGroup;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (elementGroup.children.length > 0) {
|
||||||
|
sceneGroup.add(elementGroup);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fecha o modelo após extração para liberar memória
|
|
||||||
try {
|
try {
|
||||||
ifcApi.CloseModel(modelID);
|
ifcApi.CloseModel(modelID);
|
||||||
} catch {}
|
} catch {}
|
||||||
@@ -649,7 +714,7 @@ export async function loadAndAuditIFC(
|
|||||||
qualityMessage = 'Modelo exportado no padrão AISC EM.11 (SteelFabricationView). Todas as montagens e marcas principais de conjuntos estão 100% integradas nativamente.';
|
qualityMessage = 'Modelo exportado no padrão AISC EM.11 (SteelFabricationView). Todas as montagens e marcas principais de conjuntos estão 100% integradas nativamente.';
|
||||||
} else if (totalWeldedGroups > 0 || uniqueMarksSet.size > 0) {
|
} else if (totalWeldedGroups > 0 || uniqueMarksSet.size > 0) {
|
||||||
qualityScore = 'BOM';
|
qualityScore = 'BOM';
|
||||||
qualityMessage = `Modo Avançado Ativado: ${uniqueMarksSet.size} marcas de peças e ${totalWeldedGroups} conjuntos topológicos identificados com sucesso.`;
|
qualityMessage = `Modo SteelXR Ativado: ${uniqueMarksSet.size} marcas e conjuntos identificados com qualidade gráfica calibrada.`;
|
||||||
recommendation = 'O modelo está totalmente operacional e sincronizado com os apontamentos da fábrica.';
|
recommendation = 'O modelo está totalmente operacional e sincronizado com os apontamentos da fábrica.';
|
||||||
} else {
|
} else {
|
||||||
qualityScore = 'ATENÇÃO';
|
qualityScore = 'ATENÇÃO';
|
||||||
@@ -685,7 +750,7 @@ export async function loadAndAuditIFC(
|
|||||||
piecesByMark,
|
piecesByMark,
|
||||||
pieceByExpressID,
|
pieceByExpressID,
|
||||||
weldedGroupMap,
|
weldedGroupMap,
|
||||||
materialsByColor: materialCache as any,
|
materialsByColor: materialsCache as any,
|
||||||
totalMeshes
|
totalMeshes
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user