fix(visualizador3d): correcao da geometria estourada e mapeamento de cores por apontamento
This commit is contained in:
@@ -233,31 +233,45 @@ export const ModelViewer3D: React.FC<ModelViewer3DProps> = ({
|
|||||||
group.traverse((child) => {
|
group.traverse((child) => {
|
||||||
if ((child as THREE.Mesh).isMesh) {
|
if ((child as THREE.Mesh).isMesh) {
|
||||||
const mesh = child as THREE.Mesh;
|
const mesh = child as THREE.Mesh;
|
||||||
const pmark = mesh.userData.pieceMark || '';
|
const pmark = String(mesh.userData.pieceMark || '').trim();
|
||||||
|
const cleanMark = String(mesh.userData.cleanMark || pmark).trim();
|
||||||
|
|
||||||
if (colorMode === 'production' && productionData) {
|
if (colorMode === 'production' && productionData) {
|
||||||
// Look up production pointing status
|
// Look up production pointing status: tenta pmark, depois cleanMark, depois busca flexível
|
||||||
let prod = productionData.get(pmark);
|
let prod = productionData.get(pmark) || productionData.get(cleanMark);
|
||||||
if (!prod) {
|
if (!prod) {
|
||||||
// Fallback lookup: IFC marks sometimes have prefixes like B135-1-V1 or suffixes.
|
|
||||||
for (const [key, val] of productionData.entries()) {
|
for (const [key, val] of productionData.entries()) {
|
||||||
if (pmark === key || pmark.endsWith(`-${key}`) || key.endsWith(`-${pmark}`) || pmark.includes(key)) {
|
if (
|
||||||
|
pmark === key ||
|
||||||
|
cleanMark === key ||
|
||||||
|
pmark === val.marca ||
|
||||||
|
cleanMark === val.marca ||
|
||||||
|
pmark.endsWith(`-${key}`) ||
|
||||||
|
pmark.endsWith(`-${val.marca}`) ||
|
||||||
|
key.endsWith(`-${pmark}`) ||
|
||||||
|
key.endsWith(`-${cleanMark}`)
|
||||||
|
) {
|
||||||
prod = val;
|
prod = val;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug log (somente primeira vez para as primeiras 5 peças para não travar o console)
|
// Debug log (primeiras 10 peças para validação no console)
|
||||||
if ((window as any)._debugIfcColors === undefined) (window as any)._debugIfcColors = 0;
|
if ((window as any)._debugIfcColors === undefined) (window as any)._debugIfcColors = 0;
|
||||||
if ((window as any)._debugIfcColors < 10) {
|
if ((window as any)._debugIfcColors < 10) {
|
||||||
console.log(`[IFC Color Debug] IFC pmark: "${pmark}" -> DB match:`, prod ? `Achou! Marca DB: "${prod.marca}", Qtd: ${prod.pointedQtd}, Cor: ${prod.processColor}` : `NÃO ACHOU. Chaves disponíveis: ${Array.from(productionData.keys()).slice(0,5).join(', ')}...`);
|
console.log(
|
||||||
|
`[IFC Color Debug] IFC pmark: "${pmark}" (clean: "${cleanMark}") -> DB match:`,
|
||||||
|
prod
|
||||||
|
? `ACHOU! Marca DB: "${prod.marca}", Apontadas: ${prod.pointedQtd}/${prod.totalQtd}, Processo: "${prod.currentProcessName}", Cor: ${prod.processColor}`
|
||||||
|
: `NÃO ACHOU. Chaves DB disponíveis: ${Array.from(productionData.keys()).slice(0, 8).join(', ')}...`
|
||||||
|
);
|
||||||
(window as any)._debugIfcColors++;
|
(window as any)._debugIfcColors++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prod && prod.pointedQtd > 0) {
|
if (prod && prod.pointedQtd > 0) {
|
||||||
// Apply stage color (e.g., Light Green #4ade80 for pointed)
|
// Peça apontada: aplica a cor do estágio de fabricação
|
||||||
const stageHex = prod.processColor || '#4ade80';
|
const stageHex = prod.processColor || '#10b981';
|
||||||
mesh.material = new THREE.MeshStandardMaterial({
|
mesh.material = new THREE.MeshStandardMaterial({
|
||||||
color: new THREE.Color(stageHex),
|
color: new THREE.Color(stageHex),
|
||||||
metalness: 0.35,
|
metalness: 0.35,
|
||||||
@@ -265,11 +279,11 @@ export const ModelViewer3D: React.FC<ModelViewer3DProps> = ({
|
|||||||
side: THREE.DoubleSide,
|
side: THREE.DoubleSide,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Unpointed / Pending (Default Gray)
|
// Não apontada / Pendente: Cinza claro padrão industrial
|
||||||
mesh.material = new THREE.MeshStandardMaterial({
|
mesh.material = new THREE.MeshStandardMaterial({
|
||||||
color: 0x64748b,
|
color: 0x94a3b8,
|
||||||
metalness: 0.3,
|
metalness: 0.25,
|
||||||
roughness: 0.6,
|
roughness: 0.65,
|
||||||
transparent: opacity < 100,
|
transparent: opacity < 100,
|
||||||
opacity: opacity / 100,
|
opacity: opacity / 100,
|
||||||
side: THREE.DoubleSide,
|
side: THREE.DoubleSide,
|
||||||
|
|||||||
+303
-102
@@ -27,6 +27,7 @@ export interface PieceInfo {
|
|||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
pieceMark: string;
|
pieceMark: string;
|
||||||
|
cleanMark?: string;
|
||||||
assemblyMark?: string;
|
assemblyMark?: string;
|
||||||
parentAssemblyID?: number;
|
parentAssemblyID?: number;
|
||||||
phase?: string;
|
phase?: string;
|
||||||
@@ -44,34 +45,95 @@ export interface LoadedIFCResult {
|
|||||||
audit: IFCQualityAudit;
|
audit: IFCQualityAudit;
|
||||||
piecesByMark: Map<string, PieceInfo[]>;
|
piecesByMark: Map<string, PieceInfo[]>;
|
||||||
pieceByExpressID: Map<number, PieceInfo>;
|
pieceByExpressID: Map<number, PieceInfo>;
|
||||||
weldedGroupMap: Map<number, number[]>; // expressID -> all connected expressIDs
|
weldedGroupMap: Map<number, number[]>;
|
||||||
materialsByColor: Map<string, THREE.Material>;
|
materialsByColor: Map<string, THREE.Material>;
|
||||||
totalMeshes: number;
|
totalMeshes: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: Palette by Section/Description (Mode "Descrição")
|
// Paleta por Descrição/Seção (Modo "Descrição")
|
||||||
const DESCRIPTION_PALETTE = [
|
const DESCRIPTION_PALETTE = [
|
||||||
'#3b82f6', // blue
|
'#3b82f6', // azul
|
||||||
'#ec4899', // pink/magenta
|
'#ec4899', // rosa
|
||||||
'#10b981', // emerald
|
'#10b981', // esmeralda
|
||||||
'#f59e0b', // amber
|
'#f59e0b', // ambar
|
||||||
'#8b5cf6', // purple
|
'#8b5cf6', // roxo
|
||||||
'#06b6d4', // cyan
|
'#06b6d4', // ciano
|
||||||
'#ef4444', // red
|
'#ef4444', // vermelho
|
||||||
'#84cc16', // lime
|
'#84cc16', // lima
|
||||||
'#6366f1', // indigo
|
'#6366f1', // indigo
|
||||||
'#14b8a6', // teal
|
'#14b8a6', // teal
|
||||||
'#f97316', // orange
|
'#f97316', // laranja
|
||||||
'#eab308', // yellow
|
'#eab308', // amarelo
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Helper: Valida se um valor extraído é uma marca útil real
|
||||||
|
export function isValidMark(val: any): boolean {
|
||||||
|
if (val === null || val === undefined) return false;
|
||||||
|
const s = String(val).trim();
|
||||||
|
if (s.length === 0) return false;
|
||||||
|
const lower = s.toLowerCase();
|
||||||
|
if (
|
||||||
|
lower === '-' ||
|
||||||
|
lower === '?' ||
|
||||||
|
lower === 'indefinido' ||
|
||||||
|
lower === 'undefined' ||
|
||||||
|
lower === 'null' ||
|
||||||
|
lower === 'none' ||
|
||||||
|
lower === 'default' ||
|
||||||
|
lower === 'n/a' ||
|
||||||
|
lower === 'na' ||
|
||||||
|
lower === '$'
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Extrai campos formatados comuns em Description (ex: "Mark:TEST10 Pos:TEST10 Material:A36")
|
||||||
|
export function parseDescriptionFields(desc: string): Record<string, string> {
|
||||||
|
const result: Record<string, string> = {};
|
||||||
|
if (!desc || typeof desc !== 'string') return result;
|
||||||
|
|
||||||
|
const markMatch = desc.match(/(?:Mark|Marca|Part|Piece)[:\s=]*([^\s,;]+)/i);
|
||||||
|
if (markMatch && isValidMark(markMatch[1])) result.mark = markMatch[1].trim();
|
||||||
|
|
||||||
|
const posMatch = desc.match(/(?:Pos|Posição|Posicao)[:\s=]*([^\s,;]+)/i);
|
||||||
|
if (posMatch && isValidMark(posMatch[1])) result.pos = posMatch[1].trim();
|
||||||
|
|
||||||
|
const matMatch = desc.match(/(?:Material|Mat)[:\s=]*([^\s,;]+)/i);
|
||||||
|
if (matMatch && isValidMark(matMatch[1])) result.material = matMatch[1].trim();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Extrai o valor limpo da marca (ex: "B135-1-4" -> "4", "Pos: 4" -> "4")
|
||||||
|
export function getCleanMarkValue(rawMark: string): string {
|
||||||
|
if (!rawMark) return '';
|
||||||
|
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('-')) {
|
||||||
|
const parts = cleaned.split('-');
|
||||||
|
const last = parts[parts.length - 1].trim();
|
||||||
|
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);
|
||||||
|
if (prefixMatch && isValidMark(prefixMatch[1])) {
|
||||||
|
return prefixMatch[1].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleaned;
|
||||||
|
}
|
||||||
|
|
||||||
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. Initialize Web-IFC WASM locally with fallback
|
// 1. Inicializa Web-IFC WASM com fallback
|
||||||
if (onProgress) onProgress(10, 'Carregando motor Web-IFC (WASM)...');
|
if (onProgress) onProgress(10, 'Carregando motor Web-IFC (WASM)...');
|
||||||
|
|
||||||
const LOCAL_WASM_PATH = '/wasm/';
|
const LOCAL_WASM_PATH = '/wasm/';
|
||||||
@@ -115,7 +177,7 @@ export async function loadAndAuditIFC(
|
|||||||
throw new Error('Não foi possível inicializar o motor Web-IFC WebAssembly.');
|
throw new Error('Não foi possível inicializar o motor Web-IFC WebAssembly.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Read bytes & Raw String for Header
|
// 2. Leitura dos dados binários e cabeçalho
|
||||||
if (onProgress) onProgress(25, 'Lendo arquivo IFC...');
|
if (onProgress) onProgress(25, 'Lendo arquivo IFC...');
|
||||||
let data: Uint8Array;
|
let data: Uint8Array;
|
||||||
let rawHeader = '';
|
let rawHeader = '';
|
||||||
@@ -130,7 +192,7 @@ export async function loadAndAuditIFC(
|
|||||||
rawHeader = decoder.decode(data.slice(0, 4096));
|
rawHeader = decoder.decode(data.slice(0, 4096));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Open Model
|
// 3. Abertura do Modelo com suporte a coordenadas relativas
|
||||||
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,
|
COORDINATE_TO_ORIGIN: true,
|
||||||
@@ -140,10 +202,10 @@ export async function loadAndAuditIFC(
|
|||||||
throw new Error('Falha ao decodificar a estrutura do arquivo IFC.');
|
throw new Error('Falha ao decodificar a estrutura do arquivo IFC.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Header Detection
|
// 4. Detecção de Header
|
||||||
const isEM11 = rawHeader.includes('SteelFabricationView') || rawHeader.includes('EM.11');
|
const isEM11 = rawHeader.includes('SteelFabricationView') || rawHeader.includes('EM.11');
|
||||||
|
|
||||||
// Collect Elements
|
// Coleta de entidades estruturais
|
||||||
if (onProgress) onProgress(50, 'Mapeando topologia estrutural e soldas...');
|
if (onProgress) onProgress(50, 'Mapeando topologia estrutural e soldas...');
|
||||||
const assemblies = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCELEMENTASSEMBLY);
|
const assemblies = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCELEMENTASSEMBLY);
|
||||||
const beams = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCBEAM);
|
const beams = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCBEAM);
|
||||||
@@ -160,9 +222,10 @@ export async function loadAndAuditIFC(
|
|||||||
const totalFasteners = fasteners.size();
|
const totalFasteners = fasteners.size();
|
||||||
const totalStructural = totalBeams + totalColumns + totalMembers + totalPlates;
|
const totalStructural = totalBeams + totalColumns + totalMembers + totalPlates;
|
||||||
|
|
||||||
// 4.1 Native Aggregates mapping (IFCRELAGGREGATES)
|
// 4.1 Mapeamento nativo de montagens (IFCRELAGGREGATES)
|
||||||
const aggregates = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCRELAGGREGATES);
|
const aggregates = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCRELAGGREGATES);
|
||||||
const assemblyToChildren = new Map<number, number[]>();
|
const assemblyToChildren = new Map<number, number[]>();
|
||||||
|
const childToAssembly = new Map<number, number>();
|
||||||
|
|
||||||
for (let i = 0; i < aggregates.size(); i++) {
|
for (let i = 0; i < aggregates.size(); i++) {
|
||||||
const relID = aggregates.get(i);
|
const relID = aggregates.get(i);
|
||||||
@@ -172,14 +235,16 @@ export async function loadAndAuditIFC(
|
|||||||
const parentID = rel.RelatingObject.value;
|
const parentID = rel.RelatingObject.value;
|
||||||
const childIDs = (rel.RelatedObjects as any[]).map((r: any) => r.value);
|
const childIDs = (rel.RelatedObjects as any[]).map((r: any) => r.value);
|
||||||
assemblyToChildren.set(parentID, childIDs);
|
assemblyToChildren.set(parentID, childIDs);
|
||||||
|
for (const cId of childIDs) {
|
||||||
|
childToAssembly.set(cId, parentID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4.2 FALLBACK: Topologic Weld Graph (IFCRELCONNECTSWITHREALIZINGELEMENTS)
|
// 4.2 Grafo de Soldas (IFCRELCONNECTSWITHREALIZINGELEMENTS)
|
||||||
// Connects parts (Plates <-> Columns <-> Beams) when IfcElementAssembly is absent
|
|
||||||
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>>();
|
||||||
|
|
||||||
@@ -204,7 +269,7 @@ export async function loadAndAuditIFC(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute Connected Components for Welded Assemblies (DSU/BFS)
|
// 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;
|
||||||
@@ -234,7 +299,7 @@ export async function loadAndAuditIFC(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4.3 Element Properties Map from IFCRELDEFINESBYPROPERTIES
|
// 4.3 Indexação profunda de propriedades (IFCRELDEFINESBYPROPERTIES)
|
||||||
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);
|
||||||
@@ -246,30 +311,60 @@ export async function loadAndAuditIFC(
|
|||||||
const propDefId = rel.RelatingPropertyDefinition.value;
|
const propDefId = rel.RelatingPropertyDefinition.value;
|
||||||
if (propDefId && typeof propDefId === 'number' && propDefId > 0) {
|
if (propDefId && typeof propDefId === 'number' && propDefId > 0) {
|
||||||
const propDef = ifcApi.GetLine(modelID, propDefId);
|
const propDef = ifcApi.GetLine(modelID, propDefId);
|
||||||
if (propDef && propDef.HasProperties) {
|
if (propDef) {
|
||||||
const props: Record<string, string> = {};
|
const props: Record<string, string> = {};
|
||||||
for (const pRef of propDef.HasProperties) {
|
|
||||||
const pId = pRef?.value;
|
// Leitura de HasProperties (Pset_*)
|
||||||
if (pId && typeof pId === 'number' && pId > 0) {
|
if (propDef.HasProperties && Array.isArray(propDef.HasProperties)) {
|
||||||
const pLine = ifcApi.GetLine(modelID, pId);
|
for (const pRef of propDef.HasProperties) {
|
||||||
if (pLine && pLine.Name) {
|
const pId = pRef?.value;
|
||||||
const name = pLine.Name.value;
|
if (pId && typeof pId === 'number' && pId > 0) {
|
||||||
let value = '';
|
const pLine = ifcApi.GetLine(modelID, pId);
|
||||||
if (pLine.NominalValue) {
|
if (pLine && pLine.Name) {
|
||||||
value = String(pLine.NominalValue.value);
|
const name = pLine.Name.value || pLine.Name;
|
||||||
}
|
let value = '';
|
||||||
if (name && value) {
|
if (pLine.NominalValue !== undefined && pLine.NominalValue !== null) {
|
||||||
props[name] = value;
|
if (typeof pLine.NominalValue === 'object' && pLine.NominalValue.value !== undefined) {
|
||||||
|
value = String(pLine.NominalValue.value);
|
||||||
|
} else {
|
||||||
|
value = String(pLine.NominalValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name && value && isValidMark(value)) {
|
||||||
|
props[name] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const relatedObjs = Array.isArray(rel.RelatedObjects) ? rel.RelatedObjects : [rel.RelatedObjects];
|
|
||||||
for (const objRef of relatedObjs) {
|
// Leitura de Quantities (Qto_*)
|
||||||
const objId = objRef?.value || objRef;
|
if (propDef.Quantities && Array.isArray(propDef.Quantities)) {
|
||||||
if (objId && typeof objId === 'number') {
|
for (const qRef of propDef.Quantities) {
|
||||||
const existing = elementPropertiesMap.get(objId) || {};
|
const qId = qRef?.value;
|
||||||
elementPropertiesMap.set(objId, { ...existing, ...props });
|
if (qId && typeof qId === 'number' && qId > 0) {
|
||||||
|
const qLine = ifcApi.GetLine(modelID, qId);
|
||||||
|
if (qLine && qLine.Name) {
|
||||||
|
const name = qLine.Name.value || qLine.Name;
|
||||||
|
let value = '';
|
||||||
|
if (qLine.LengthValue !== undefined) value = `${Number(qLine.LengthValue).toFixed(1)} mm`;
|
||||||
|
else if (qLine.NominalValue) value = String(qLine.NominalValue.value || qLine.NominalValue);
|
||||||
|
if (name && value && isValidMark(value)) {
|
||||||
|
props[name] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(props).length > 0) {
|
||||||
|
const relatedObjs = Array.isArray(rel.RelatedObjects) ? rel.RelatedObjects : [rel.RelatedObjects];
|
||||||
|
for (const objRef of relatedObjs) {
|
||||||
|
const objId = objRef?.value || objRef;
|
||||||
|
if (objId && typeof objId === 'number') {
|
||||||
|
const existing = elementPropertiesMap.get(objId) || {};
|
||||||
|
elementPropertiesMap.set(objId, { ...existing, ...props });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,40 +376,143 @@ export async function loadAndAuditIFC(
|
|||||||
console.debug('[IFC Loader] Property indexing note:', propErr);
|
console.debug('[IFC Loader] Property indexing note:', propErr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect piece marks & properties
|
// Função interna de extração inteligente da marca
|
||||||
|
function extractBestMark(expressID: number): {
|
||||||
|
mark: string;
|
||||||
|
cleanMark: string;
|
||||||
|
section: string;
|
||||||
|
description: string;
|
||||||
|
} {
|
||||||
|
const props = elementPropertiesMap.get(expressID) || {};
|
||||||
|
|
||||||
|
let rawName = '';
|
||||||
|
let rawTag = '';
|
||||||
|
let rawDesc = '';
|
||||||
|
let rawObjType = '';
|
||||||
|
try {
|
||||||
|
const rawLine = ifcApi.GetLine(modelID, expressID);
|
||||||
|
if (rawLine) {
|
||||||
|
rawName = rawLine.Name?.value ?? (typeof rawLine.Name === 'string' ? rawLine.Name : '');
|
||||||
|
rawTag = rawLine.Tag?.value ?? (typeof rawLine.Tag === 'string' ? rawLine.Tag : '');
|
||||||
|
rawDesc = rawLine.Description?.value ?? (typeof rawLine.Description === 'string' ? rawLine.Description : '');
|
||||||
|
rawObjType = rawLine.ObjectType?.value ?? (typeof rawLine.ObjectType === 'string' ? rawLine.ObjectType : '');
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
const descParsed = parseDescriptionFields(rawDesc);
|
||||||
|
|
||||||
|
// Prioridade de propriedades que representam Marca/Posição da peça
|
||||||
|
const candidates: (string | undefined)[] = [
|
||||||
|
props.PieceMark,
|
||||||
|
props.PartMark,
|
||||||
|
props.SinglePartMark,
|
||||||
|
props.MainPartMark,
|
||||||
|
props.Marca,
|
||||||
|
props.Pos,
|
||||||
|
props.Posição,
|
||||||
|
props.Posicao,
|
||||||
|
descParsed.pos,
|
||||||
|
descParsed.mark,
|
||||||
|
props.Mark,
|
||||||
|
props['Part Number'],
|
||||||
|
props.PartPos,
|
||||||
|
props['Item Number'],
|
||||||
|
props.ItemNumber,
|
||||||
|
props.PartNumber,
|
||||||
|
rawTag,
|
||||||
|
props.Tag,
|
||||||
|
props.Reference, // Validado por isValidMark
|
||||||
|
rawObjType,
|
||||||
|
rawName
|
||||||
|
];
|
||||||
|
|
||||||
|
let chosenMark = '';
|
||||||
|
for (const cand of candidates) {
|
||||||
|
if (cand && isValidMark(cand)) {
|
||||||
|
chosenMark = String(cand).trim();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se não encontrou no próprio elemento, tenta herdar da montagem pai
|
||||||
|
if (!chosenMark && childToAssembly.has(expressID)) {
|
||||||
|
const parentId = childToAssembly.get(expressID)!;
|
||||||
|
const parentProps = elementPropertiesMap.get(parentId) || {};
|
||||||
|
try {
|
||||||
|
const parentLine = ifcApi.GetLine(modelID, parentId);
|
||||||
|
const parentName = parentLine?.Name?.value ?? '';
|
||||||
|
const parentTag = parentLine?.Tag?.value ?? '';
|
||||||
|
const parentCandidates = [
|
||||||
|
parentProps.PieceMark,
|
||||||
|
parentProps.AssemblyMark,
|
||||||
|
parentProps['Assembly Mark'],
|
||||||
|
parentProps.Marca,
|
||||||
|
parentProps.Mark,
|
||||||
|
parentProps.Pos,
|
||||||
|
parentTag,
|
||||||
|
parentName
|
||||||
|
];
|
||||||
|
for (const pCand of parentCandidates) {
|
||||||
|
if (pCand && isValidMark(pCand)) {
|
||||||
|
chosenMark = String(pCand).trim();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const section =
|
||||||
|
props.Section ||
|
||||||
|
props.Profile ||
|
||||||
|
props.Perfil ||
|
||||||
|
props.Description ||
|
||||||
|
rawDesc ||
|
||||||
|
rawName ||
|
||||||
|
'';
|
||||||
|
|
||||||
|
const clean = getCleanMarkValue(chosenMark);
|
||||||
|
|
||||||
|
return {
|
||||||
|
mark: chosenMark || 'indefinido',
|
||||||
|
cleanMark: clean || chosenMark || 'indefinido',
|
||||||
|
section,
|
||||||
|
description: rawDesc
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coleta de montagens nativas
|
||||||
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>();
|
const sectionColorMap = new Map<string, string>();
|
||||||
let colorIndex = 0;
|
let colorIndex = 0;
|
||||||
|
|
||||||
// Process Native Assemblies if present
|
|
||||||
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);
|
||||||
try {
|
try {
|
||||||
const assObj = ifcApi.GetLine(modelID, assID);
|
const assObj = ifcApi.GetLine(modelID, assID);
|
||||||
const props = elementPropertiesMap.get(assID) || {};
|
const markInfo = extractBestMark(assID);
|
||||||
const rawProps = getSyncProperties(ifcApi, modelID, assID);
|
|
||||||
const pmark = props.PieceMark || props.Reference || props.Mark || props.Tag || rawProps.Name || 'indefinido';
|
|
||||||
|
|
||||||
const children = assemblyToChildren.get(assID) || [];
|
const children = assemblyToChildren.get(assID) || [];
|
||||||
|
|
||||||
const piece: PieceInfo = {
|
const piece: PieceInfo = {
|
||||||
expressID: assID,
|
expressID: assID,
|
||||||
guid: assObj?.GlobalId?.value || '',
|
guid: assObj?.GlobalId?.value || '',
|
||||||
name: assObj?.Name?.value || 'Montagem Estrutural',
|
name: assObj?.Name?.value || 'Montagem Estrutural',
|
||||||
type: 'IFCELEMENTASSEMBLY',
|
type: 'IFCELEMENTASSEMBLY',
|
||||||
pieceMark: pmark,
|
pieceMark: markInfo.mark,
|
||||||
|
cleanMark: markInfo.cleanMark,
|
||||||
childrenIDs: children,
|
childrenIDs: children,
|
||||||
connectedMeshIDs: children,
|
connectedMeshIDs: children,
|
||||||
section: props.Section || props.Profile,
|
section: markInfo.section,
|
||||||
phase: props.Phase || extractPhaseFromMark(pmark),
|
phase: extractPhaseFromMark(markInfo.mark),
|
||||||
ofNumber: extractOFFromMark(pmark)
|
ofNumber: extractOFFromMark(markInfo.mark)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (pmark !== 'indefinido') {
|
if (markInfo.mark !== 'indefinido') {
|
||||||
uniqueMarksSet.add(pmark);
|
uniqueMarksSet.add(markInfo.mark);
|
||||||
if (!piecesByMark.has(pmark)) piecesByMark.set(pmark, []);
|
if (markInfo.cleanMark !== 'indefinido') uniqueMarksSet.add(markInfo.cleanMark);
|
||||||
piecesByMark.get(pmark)!.push(piece);
|
if (!piecesByMark.has(markInfo.mark)) piecesByMark.set(markInfo.mark, []);
|
||||||
|
piecesByMark.get(markInfo.mark)!.push(piece);
|
||||||
}
|
}
|
||||||
pieceByExpressID.set(assID, piece);
|
pieceByExpressID.set(assID, piece);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -322,34 +520,28 @@ export async function loadAndAuditIFC(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Generate 3D Meshes with Three.js
|
// 5. Geração de Malhas 3D com Three.js corrigindo o empacotamento de vértices/normais
|
||||||
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 defaultGrayMat = new THREE.MeshStandardMaterial({
|
||||||
color: 0x94a3b8,
|
color: 0x94a3b8,
|
||||||
metalness: 0.35,
|
metalness: 0.3,
|
||||||
roughness: 0.55,
|
roughness: 0.6,
|
||||||
side: THREE.DoubleSide
|
side: THREE.DoubleSide
|
||||||
});
|
});
|
||||||
|
|
||||||
const materialCache = new Map<string, THREE.MeshStandardMaterial>();
|
const materialCache = new Map<string, THREE.MeshStandardMaterial>();
|
||||||
|
|
||||||
let totalMeshes = 0;
|
let totalMeshes = 0;
|
||||||
|
|
||||||
ifcApi.StreamAllMeshes(modelID, (flatMesh: WebIFC.FlatMesh) => {
|
ifcApi.StreamAllMeshes(modelID, (flatMesh: WebIFC.FlatMesh) => {
|
||||||
const expressID = flatMesh.expressID;
|
const expressID = flatMesh.expressID;
|
||||||
const placedGeometries = flatMesh.geometries;
|
const placedGeometries = flatMesh.geometries;
|
||||||
|
|
||||||
// Check properties for this element
|
const markInfo = extractBestMark(expressID);
|
||||||
const props = elementPropertiesMap.get(expressID) || {};
|
const colorKey = markInfo.section || 'PADRAO';
|
||||||
const rawProps = getSyncProperties(ifcApi, modelID, expressID);
|
|
||||||
const pmark = props.PieceMark || props.Reference || props.Mark || props.Tag || rawProps.Name || '';
|
|
||||||
const section = props.Section || props.Profile || props.Description || rawProps.Name || '';
|
|
||||||
|
|
||||||
// Determine section color
|
|
||||||
const colorKey = section || 'PADRAO';
|
|
||||||
if (!sectionColorMap.has(colorKey)) {
|
if (!sectionColorMap.has(colorKey)) {
|
||||||
sectionColorMap.set(colorKey, DESCRIPTION_PALETTE[colorIndex % DESCRIPTION_PALETTE.length]);
|
sectionColorMap.set(colorKey, DESCRIPTION_PALETTE[colorIndex % DESCRIPTION_PALETTE.length]);
|
||||||
colorIndex++;
|
colorIndex++;
|
||||||
@@ -361,61 +553,78 @@ export async function loadAndAuditIFC(
|
|||||||
hexColor,
|
hexColor,
|
||||||
new THREE.MeshStandardMaterial({
|
new THREE.MeshStandardMaterial({
|
||||||
color: new THREE.Color(hexColor),
|
color: new THREE.Color(hexColor),
|
||||||
metalness: 0.4,
|
metalness: 0.35,
|
||||||
roughness: 0.5,
|
roughness: 0.55,
|
||||||
side: THREE.DoubleSide
|
side: THREE.DoubleSide
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const mat = materialCache.get(hexColor) || defaultGrayMat;
|
const mat = materialCache.get(hexColor) || defaultGrayMat;
|
||||||
|
|
||||||
// Get welded connected peers (Fallback topology)
|
|
||||||
const connectedGroup = weldedGroupMap.get(expressID) || [expressID];
|
const connectedGroup = weldedGroupMap.get(expressID) || [expressID];
|
||||||
|
|
||||||
for (let g = 0; g < placedGeometries.size(); g++) {
|
for (let g = 0; g < placedGeometries.size(); g++) {
|
||||||
const placedGeo = placedGeometries.get(g);
|
const placedGeo = placedGeometries.get(g);
|
||||||
const ifcGeo = ifcApi.GetGeometry(modelID, placedGeo.geometryExpressID);
|
const ifcGeo = ifcApi.GetGeometry(modelID, placedGeo.geometryExpressID);
|
||||||
|
|
||||||
const positions = ifcApi.GetVertexArray(ifcGeo.GetVertexData(), ifcGeo.GetVertexDataSize());
|
// Web-IFC GetVertexArray retorna [px, py, pz, nx, ny, nz, ...] (interleaved de 6 floats)
|
||||||
|
const verts = ifcApi.GetVertexArray(ifcGeo.GetVertexData(), ifcGeo.GetVertexDataSize());
|
||||||
const indices = ifcApi.GetIndexArray(ifcGeo.GetIndexData(), ifcGeo.GetIndexDataSize());
|
const indices = ifcApi.GetIndexArray(ifcGeo.GetIndexData(), ifcGeo.GetIndexDataSize());
|
||||||
|
|
||||||
const bufferGeo = new THREE.BufferGeometry();
|
const bufferGeo = new THREE.BufferGeometry();
|
||||||
bufferGeo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3));
|
const numVerts = verts.length / 6;
|
||||||
|
const positionArray = new Float32Array(numVerts * 3);
|
||||||
|
const normalArray = new Float32Array(numVerts * 3);
|
||||||
|
|
||||||
|
for (let j = 0; j < verts.length; j += 6) {
|
||||||
|
const idx = j / 6;
|
||||||
|
positionArray[idx * 3] = verts[j];
|
||||||
|
positionArray[idx * 3 + 1] = verts[j + 1];
|
||||||
|
positionArray[idx * 3 + 2] = verts[j + 2];
|
||||||
|
normalArray[idx * 3] = verts[j + 3];
|
||||||
|
normalArray[idx * 3 + 1] = verts[j + 4];
|
||||||
|
normalArray[idx * 3 + 2] = verts[j + 5];
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferGeo.setAttribute('position', new THREE.BufferAttribute(positionArray, 3));
|
||||||
|
bufferGeo.setAttribute('normal', new THREE.BufferAttribute(normalArray, 3));
|
||||||
bufferGeo.setIndex(new THREE.BufferAttribute(new Uint32Array(indices), 1));
|
bufferGeo.setIndex(new THREE.BufferAttribute(new Uint32Array(indices), 1));
|
||||||
bufferGeo.computeVertexNormals();
|
|
||||||
|
|
||||||
const mesh = new THREE.Mesh(bufferGeo, mat);
|
const mesh = new THREE.Mesh(bufferGeo, mat);
|
||||||
mesh.matrixAutoUpdate = false;
|
const matrix = new THREE.Matrix4();
|
||||||
mesh.matrix.fromArray(placedGeo.flatTransformation);
|
matrix.fromArray(placedGeo.flatTransformation);
|
||||||
mesh.matrixWorldNeedsUpdate = true;
|
mesh.applyMatrix4(matrix);
|
||||||
|
|
||||||
mesh.userData = {
|
mesh.userData = {
|
||||||
expressID,
|
expressID,
|
||||||
pieceMark: pmark,
|
pieceMark: markInfo.mark,
|
||||||
section,
|
cleanMark: markInfo.cleanMark,
|
||||||
|
section: markInfo.section,
|
||||||
colorKey,
|
colorKey,
|
||||||
connectedMeshIDs: connectedGroup
|
connectedMeshIDs: connectedGroup
|
||||||
};
|
};
|
||||||
|
|
||||||
sceneGroup.add(mesh);
|
sceneGroup.add(mesh);
|
||||||
|
ifcGeo.delete();
|
||||||
totalMeshes++;
|
totalMeshes++;
|
||||||
|
|
||||||
// Register piece in lookup map
|
|
||||||
if (!pieceByExpressID.has(expressID)) {
|
if (!pieceByExpressID.has(expressID)) {
|
||||||
const piece: PieceInfo = {
|
const piece: PieceInfo = {
|
||||||
expressID,
|
expressID,
|
||||||
guid: '',
|
guid: '',
|
||||||
name: section || 'Peça Estrutural',
|
name: markInfo.section || 'Peça Estrutural',
|
||||||
type: 'ELEMENT',
|
type: 'ELEMENT',
|
||||||
pieceMark: pmark,
|
pieceMark: markInfo.mark,
|
||||||
section,
|
cleanMark: markInfo.cleanMark,
|
||||||
|
section: markInfo.section,
|
||||||
connectedMeshIDs: connectedGroup,
|
connectedMeshIDs: connectedGroup,
|
||||||
mesh
|
mesh
|
||||||
};
|
};
|
||||||
pieceByExpressID.set(expressID, piece);
|
pieceByExpressID.set(expressID, piece);
|
||||||
if (pmark && pmark !== 'indefinido') {
|
if (markInfo.mark && markInfo.mark !== 'indefinido') {
|
||||||
uniqueMarksSet.add(pmark);
|
uniqueMarksSet.add(markInfo.mark);
|
||||||
if (!piecesByMark.has(pmark)) piecesByMark.set(pmark, []);
|
if (markInfo.cleanMark !== 'indefinido') uniqueMarksSet.add(markInfo.cleanMark);
|
||||||
piecesByMark.get(pmark)!.push(piece);
|
if (!piecesByMark.has(markInfo.mark)) piecesByMark.set(markInfo.mark, []);
|
||||||
|
piecesByMark.get(markInfo.mark)!.push(piece);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pieceByExpressID.get(expressID)!.mesh = mesh;
|
pieceByExpressID.get(expressID)!.mesh = mesh;
|
||||||
@@ -424,7 +633,12 @@ export async function loadAndAuditIFC(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Calculate Quality Audit & Fallback Status
|
// Fecha o modelo após extração para liberar memória
|
||||||
|
try {
|
||||||
|
ifcApi.CloseModel(modelID);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
// Auditoria de Qualidade
|
||||||
let qualityScore: 'EXCELENTE' | 'BOM' | 'ATENÇÃO' = 'ATENÇÃO';
|
let qualityScore: 'EXCELENTE' | 'BOM' | 'ATENÇÃO' = 'ATENÇÃO';
|
||||||
let qualityMessage = '';
|
let qualityMessage = '';
|
||||||
let recommendation = '';
|
let recommendation = '';
|
||||||
@@ -435,8 +649,8 @@ 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 Fallback Ativado: ${totalWeldedGroups} conjuntos soldados identificados por conectividade topológica (IFCRELCONNECTS). O modelo amarrou as peças filhas e avulsas aos seus blocos estruturais correspondentes.`;
|
qualityMessage = `Modo Avançado Ativado: ${uniqueMarksSet.size} marcas de peças e ${totalWeldedGroups} conjuntos topológicos identificados com sucesso.`;
|
||||||
recommendation = 'O modelo está totalmente operacional através do Fallback de Soldas. Para máxima compatibilidade nativa, utilize a opção "IFC MDV EM.11" nas exportações futuras do Advance Steel.';
|
recommendation = 'O modelo está totalmente operacional e sincronizado com os apontamentos da fábrica.';
|
||||||
} else {
|
} else {
|
||||||
qualityScore = 'ATENÇÃO';
|
qualityScore = 'ATENÇÃO';
|
||||||
qualityMessage = 'Não foram identificadas marcas de peças no padrão da fábrica nas propriedades do IFC.';
|
qualityMessage = 'Não foram identificadas marcas de peças no padrão da fábrica nas propriedades do IFC.';
|
||||||
@@ -476,19 +690,6 @@ export async function loadAndAuditIFC(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers
|
|
||||||
|
|
||||||
function getSyncProperties(ifcApi: WebIFC.IfcAPI, modelID: number, expressID: number): Record<string, string> {
|
|
||||||
const result: Record<string, string> = {};
|
|
||||||
try {
|
|
||||||
const raw = ifcApi.GetLine(modelID, expressID);
|
|
||||||
if (raw && raw.Name && raw.Name.value) {
|
|
||||||
result.Name = raw.Name.value;
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractPhaseFromMark(mark: string): string {
|
function extractPhaseFromMark(mark: string): string {
|
||||||
const parts = mark.split('-');
|
const parts = mark.split('-');
|
||||||
if (parts.length >= 3) return parts[1];
|
if (parts.length >= 3) return parts[1];
|
||||||
|
|||||||
@@ -114,9 +114,9 @@ export default function Visualizador3D() {
|
|||||||
console.error("Erro ao buscar apontamentos no visualizador:", errorApontamentos);
|
console.error("Erro ao buscar apontamentos no visualizador:", errorApontamentos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build phases list
|
|
||||||
const phases = new Set<string>();
|
const phases = new Set<string>();
|
||||||
const prodMap = new Map<string, any>();
|
const prodMap = new Map<string, any>();
|
||||||
|
const uniqueItems: any[] = [];
|
||||||
let totalPecas = 0;
|
let totalPecas = 0;
|
||||||
let totalPeso = 0;
|
let totalPeso = 0;
|
||||||
|
|
||||||
@@ -127,17 +127,29 @@ export default function Visualizador3D() {
|
|||||||
totalPecas += Number(p.quantidade || 0);
|
totalPecas += Number(p.quantidade || 0);
|
||||||
totalPeso += Number(p.peso_total || 0);
|
totalPeso += Number(p.peso_total || 0);
|
||||||
|
|
||||||
const key = p.marca;
|
const marcaStr = String(p.marca || '').trim();
|
||||||
prodMap.set(key, {
|
const item = {
|
||||||
pecaId: p.id,
|
pecaId: p.id,
|
||||||
marca: p.marca,
|
marca: marcaStr,
|
||||||
fase,
|
fase,
|
||||||
|
ofNumber: selectedOF,
|
||||||
totalQtd: Number(p.quantidade || 1),
|
totalQtd: Number(p.quantidade || 1),
|
||||||
pointedQtd: 0,
|
pointedQtd: 0,
|
||||||
currentProcessName: 'Pendente',
|
currentProcessName: 'Pendente',
|
||||||
processColor: '#64748b',
|
processColor: '#64748b',
|
||||||
processOrdem: 0
|
processOrdem: 0
|
||||||
});
|
};
|
||||||
|
|
||||||
|
uniqueItems.push(item);
|
||||||
|
|
||||||
|
// Indexa por marca simples (ex: "4")
|
||||||
|
prodMap.set(marcaStr, item);
|
||||||
|
// Indexa por fase e marca (ex: "2-4")
|
||||||
|
prodMap.set(`${fase}-${marcaStr}`, item);
|
||||||
|
// Indexa por OF, fase e marca (ex: "B135-2-4")
|
||||||
|
prodMap.set(`${selectedOF}-${fase}-${marcaStr}`, item);
|
||||||
|
// Indexa por OF e marca (ex: "B135-4")
|
||||||
|
prodMap.set(`${selectedOF}-${marcaStr}`, item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,15 +158,19 @@ export default function Visualizador3D() {
|
|||||||
apontamentosData.sort((a: any, b: any) => (a.processo?.ordem || 0) - (b.processo?.ordem || 0));
|
apontamentosData.sort((a: any, b: any) => (a.processo?.ordem || 0) - (b.processo?.ordem || 0));
|
||||||
|
|
||||||
apontamentosData.forEach((ap: any) => {
|
apontamentosData.forEach((ap: any) => {
|
||||||
const marca = ap.peca?.marca;
|
const marca = ap.peca?.marca ? String(ap.peca.marca).trim() : '';
|
||||||
|
const fase = ap.peca?.etapa_fase ? String(ap.peca.etapa_fase).trim() : '';
|
||||||
if (marca) {
|
if (marca) {
|
||||||
const key = marca;
|
const existing =
|
||||||
const existing = prodMap.get(key);
|
prodMap.get(`${selectedOF}-${fase}-${marca}`) ||
|
||||||
|
prodMap.get(`${fase}-${marca}`) ||
|
||||||
|
prodMap.get(marca);
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
const qty = Number(ap.quantidade_produzida || 0);
|
const qty = Number(ap.quantidade_produzida || 0);
|
||||||
if (qty > 0) {
|
if (qty > 0) {
|
||||||
// Atualiza a peça para o estágio mais avançado alcançado
|
// Atualiza a peça para o estágio mais avançado alcançado
|
||||||
existing.pointedQtd = Math.max(existing.pointedQtd, qty); // Evita duplicar apontamentos de processos anteriores
|
existing.pointedQtd = Math.max(existing.pointedQtd, qty);
|
||||||
existing.currentProcessName = ap.processo?.nome || existing.currentProcessName;
|
existing.currentProcessName = ap.processo?.nome || existing.currentProcessName;
|
||||||
existing.processColor = ap.processo?.cor || PROCESS_COLORS[ap.processo?.nome || ''] || '#10b981';
|
existing.processColor = ap.processo?.cor || PROCESS_COLORS[ap.processo?.nome || ''] || '#10b981';
|
||||||
existing.processOrdem = ap.processo?.ordem || existing.processOrdem;
|
existing.processOrdem = ap.processo?.ordem || existing.processOrdem;
|
||||||
@@ -167,9 +183,10 @@ export default function Visualizador3D() {
|
|||||||
let pointedTotal = 0;
|
let pointedTotal = 0;
|
||||||
let sumProgress = 0; // Para evolução da OF
|
let sumProgress = 0; // Para evolução da OF
|
||||||
|
|
||||||
prodMap.forEach((val) => {
|
// Itera sobre uniqueItems para não duplicar métricas com os múltiplos índices de busca
|
||||||
|
uniqueItems.forEach((val) => {
|
||||||
pointedTotal += val.pointedQtd;
|
pointedTotal += val.pointedQtd;
|
||||||
// Considerando maxOrdem = 5 como final para cálculo de %. Ajuste conforme necessário.
|
// Considerando maxOrdem = 5 como final para cálculo de %.
|
||||||
const pecaProgress = val.pointedQtd > 0 ? Math.min(val.processOrdem / 5, 1) * (val.pointedQtd / val.totalQtd) : 0;
|
const pecaProgress = val.pointedQtd > 0 ? Math.min(val.processOrdem / 5, 1) * (val.pointedQtd / val.totalQtd) : 0;
|
||||||
sumProgress += pecaProgress * val.totalQtd;
|
sumProgress += pecaProgress * val.totalQtd;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user