diff --git a/.gitignore b/.gitignore index e098c12..7528f9f 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,8 @@ scripts/auto-synchome.ps1 # Arquivos temporários *.tmp *.temp + +# Arquivos de teste e dados temporários +test_*.js +test_*.cjs +*.ifc diff --git a/src/components/viewer3d/ModelViewer3D.tsx b/src/components/viewer3d/ModelViewer3D.tsx index a471921..757719d 100644 --- a/src/components/viewer3d/ModelViewer3D.tsx +++ b/src/components/viewer3d/ModelViewer3D.tsx @@ -13,6 +13,7 @@ interface ProductionPieceStatus { pointedQtd: number; currentProcessName?: string; processColor?: string; + processOrdem?: number; } interface ModelViewer3DProps { @@ -252,27 +253,46 @@ export const ModelViewer3D: React.FC = ({ const cleanAssMark = String(mesh.userData.cleanAssemblyMark || cleanMark).trim().toUpperCase(); const section = String(mesh.userData.section || '').trim().toUpperCase(); - // 1. Determinação da fase e vínculo de produção + // 1. Extração da fase deste elemento (Propriedade IFC > Regex na Marca) + let elementPhase = ''; + if (mesh.userData.phase) { + elementPhase = String(mesh.userData.phase).trim(); + } + if (!elementPhase) { + const raw = assMark || pmark; + const match = raw.match(/^(?:[A-Za-z0-9]+-)?(\d+)-/i); + if (match) { + elementPhase = match[1]; + } + } + + // 2. Determinação do vínculo de produção (com matching rigoroso de fase) let prod: ProductionPieceStatus | undefined = undefined; if (colorMode === 'production' && productionData && productionData.size > 0) { - // Prioridade 1: Busca pela Assembly Mark do conjunto estrutural - if (assMark && assMark !== 'INDEFINIDO') { - prod = productionData.get(assMark) || - productionData.get(cleanAssMark) || - (selectedPhase && selectedPhase !== 'all' ? productionData.get(`${selectedPhase}-${cleanAssMark}`.toUpperCase()) : undefined) || - (selectedOF ? productionData.get(`${selectedOF}-${cleanAssMark}`.toUpperCase()) : undefined); + const searchKeys = [ + elementPhase && selectedOF ? `${selectedOF}-${elementPhase}-${cleanAssMark}` : '', + elementPhase ? `${elementPhase}-${cleanAssMark}` : '', + elementPhase && selectedOF ? `${selectedOF}-${elementPhase}-${cleanMark}` : '', + elementPhase ? `${elementPhase}-${cleanMark}` : '', + assMark, + pmark, + cleanAssMark, + cleanMark + ].filter(Boolean).map(k => k.toUpperCase()); + + for (const key of searchKeys) { + const match = productionData.get(key); + // Se encontramos, mas ele tem fase e nós temos fase, elas precisam bater. + if (match) { + if (!elementPhase || !match.fase || String(match.fase) === String(elementPhase)) { + prod = match; + break; + } + } } - // Prioridade 2: Busca pela marca própria da peça - if (!prod && pmark && pmark !== 'INDEFINIDO') { - prod = productionData.get(pmark) || - productionData.get(cleanMark) || - (selectedPhase && selectedPhase !== 'all' ? productionData.get(`${selectedPhase}-${cleanMark}`.toUpperCase()) : undefined) || - (selectedOF ? productionData.get(`${selectedOF}-${cleanMark}`.toUpperCase()) : undefined); - } - - // Prioridade 3: Fallback para perfis / bitolas + // Fallback para perfis / bitolas if (!prod && section && section !== '-' && section !== 'INDEFINIDO') { prod = productionData.get(`PERFIL:${section}`) || productionData.get(`DESC:${section}`); @@ -291,22 +311,6 @@ export const ModelViewer3D: React.FC = ({ } } - // 2. Extração da fase deste elemento (Banco > Propriedade IFC > Regex na Marca) - let elementPhase = ''; - if (prod && prod.fase) { - elementPhase = String(prod.fase).trim(); - } - if (!elementPhase && mesh.userData.phase) { - elementPhase = String(mesh.userData.phase).trim(); - } - if (!elementPhase) { - const raw = assMark || pmark; - const match = raw.match(/^(?:[A-Za-z0-9]+-)?(\d+)-/i); - if (match) { - elementPhase = match[1]; - } - } - // 3. Controle Estrito de Visibilidade por Fase const isVisibleByPhase = !selectedPhase || @@ -330,10 +334,17 @@ export const ModelViewer3D: React.FC = ({ if (prod && prod.pointedQtd > 0) { pointedCount++; - // Peça apontada no banco: aplica com precisão a cor do estágio de fabricação - const stageHex = prod.processColor || '#10b981'; + // Cores baseadas no progresso: verde claro -> verde escuro + const qtyPercent = Math.min(prod.pointedQtd / prod.totalQtd, 1.0); + const processPercent = Math.min((prod.processOrdem || 1) / 5, 1.0); + const percent = qtyPercent * processPercent; + + const colorLight = new THREE.Color('#4ade80'); // Verde claro + const colorDark = new THREE.Color('#14532d'); // Verde escuro + const finalColor = new THREE.Color().lerpColors(colorLight, colorDark, percent); + mesh.material = new THREE.MeshStandardMaterial({ - color: new THREE.Color(stageHex), + color: finalColor, metalness: 0.35, roughness: 0.45, side: THREE.DoubleSide, diff --git a/src/lib/ifc/ifcLoaderService.ts b/src/lib/ifc/ifcLoaderService.ts index 2116c1f..b51385e 100644 --- a/src/lib/ifc/ifcLoaderService.ts +++ b/src/lib/ifc/ifcLoaderService.ts @@ -562,49 +562,49 @@ export async function loadAndAuditIFC( ''; // Extração inteligente de Fase - const phaseCandidates = [ - props.Phase, - props.Fase, - props['Phase Number'], - props['Phase Name'], - props['Tekla Assembly.Phase'], - props['Tekla Common.Phase'], - props['Tekla Assembly.Phase Name'], - props.Etapa, - props.Sequence, - props.Batch - ]; - let chosenPhase = ''; - for (const pCand of phaseCandidates) { - if (pCand && isValidMark(pCand)) { - chosenPhase = String(pCand).trim(); - break; - } - } - if (!chosenPhase && childToAssembly.has(expressID)) { - const parentId = childToAssembly.get(expressID)!; - const parentProps = elementPropertiesMap.get(parentId) || {}; - const parentPhaseCandidates = [ - parentProps.Phase, - parentProps.Fase, - parentProps['Phase Number'], - parentProps['Phase Name'], - parentProps['Tekla Assembly.Phase'], - parentProps['Tekla Common.Phase'], - parentProps['Tekla Assembly.Phase Name'], - parentProps.Etapa, - parentProps.Sequence, - parentProps.Batch + let chosenPhase = extractPhaseFromMark(assemblyMark || finalMark); + + if (!chosenPhase) { + const phaseCandidates = [ + props.Phase, + props.Fase, + props['Phase Number'], + props['Phase Name'], + props['Tekla Assembly.Phase'], + props['Tekla Common.Phase'], + props['Tekla Assembly.Phase Name'], + props.Etapa, + props.Sequence, + props.Batch ]; - for (const pCand of parentPhaseCandidates) { + for (const pCand of phaseCandidates) { if (pCand && isValidMark(pCand)) { chosenPhase = String(pCand).trim(); break; } } - } - if (!chosenPhase) { - chosenPhase = extractPhaseFromMark(assemblyMark || finalMark); + if (!chosenPhase && childToAssembly.has(expressID)) { + const parentId = childToAssembly.get(expressID)!; + const parentProps = elementPropertiesMap.get(parentId) || {}; + const parentPhaseCandidates = [ + parentProps.Phase, + parentProps.Fase, + parentProps['Phase Number'], + parentProps['Phase Name'], + parentProps['Tekla Assembly.Phase'], + parentProps['Tekla Common.Phase'], + parentProps['Tekla Assembly.Phase Name'], + parentProps.Etapa, + parentProps.Sequence, + parentProps.Batch + ]; + for (const pCand of parentPhaseCandidates) { + if (pCand && isValidMark(pCand)) { + chosenPhase = String(pCand).trim(); + break; + } + } + } } return { @@ -631,7 +631,7 @@ export async function loadAndAuditIFC( const markInfo = extractBestMark(assID); const children = assemblyToChildren.get(assID) || []; - if (markInfo.phase) { + if (markInfo.phase && markInfo.mark && markInfo.mark !== 'indefinido') { detectedPhasesSet.add(markInfo.phase); } @@ -679,7 +679,7 @@ export async function loadAndAuditIFC( const materialName = elementMaterialMap.get(expressID) ?? ''; const connectedGroup = weldedGroupMap.get(expressID) || [expressID]; - if (markInfo.phase) { + if (markInfo.phase && markInfo.mark && markInfo.mark !== 'indefinido') { detectedPhasesSet.add(markInfo.phase); } @@ -856,14 +856,13 @@ export function extractPhaseFromMark(mark: string): string { const parts = mark.split('-'); if (parts.length >= 3) { const p1 = parts[1].trim(); - if (p1 && /^\d+$/.test(p1)) return p1; - return p1; + if (p1 && /^[a-zA-Z0-9]+$/.test(p1)) return p1; // Can be 11, 12, A, etc. } if (parts.length === 2) { const p0 = parts[0].trim(); if (p0 && /^\d+$/.test(p0)) return p0; } - const match = mark.match(/(?:fase|phase|etapa)\s*(\d+)/i); + const match = mark.match(/(?:fase|phase|etapa)\s*([a-zA-Z0-9]+)/i); if (match) return match[1]; return ''; diff --git a/src/pages/Visualizador3D.tsx b/src/pages/Visualizador3D.tsx index 0d240ba..bc94999 100644 --- a/src/pages/Visualizador3D.tsx +++ b/src/pages/Visualizador3D.tsx @@ -133,8 +133,8 @@ export default function Visualizador3D() { if (pecasData) { pecasData.forEach((p: any) => { - const fase = String(p.etapa_fase || '1'); - phases.add(fase); + const fase = String(p.etapa_fase || '').trim(); + if (fase) phases.add(fase); totalPecas += Number(p.quantidade || 0); totalPeso += Number(p.peso_total || 0);