feat: contagem e selecao no 3D por peca fisica real (expressID) corrigindo contagem de sub-malhas

This commit is contained in:
2026-09-23 13:44:48 +00:00
parent b8d91d1371
commit 38bd3ee0e2
+106 -60
View File
@@ -60,12 +60,18 @@ export const ModelViewer3D: React.FC<ModelViewer3DProps> = ({
const [hoveredPiece, setHoveredPiece] = useState<PieceInfo | null>(null);
const [hoverPosition, setHoverPosition] = useState<{ x: number; y: number } | null>(null);
// Seleção Permanente Magenta (Single Click e Double Click)
const [selectedPiecesMap, setSelectedPiecesMap] = useState<Map<string, { mesh: THREE.Mesh; piece: PieceInfo }>>(new Map());
const selectedPiecesMapRef = useRef(selectedPiecesMap);
// Seleção Permanente Magenta indexada por Peça Física Real (expressID / montagem única)
interface SelectedElement {
expressID: number;
piece: PieceInfo;
meshes: THREE.Mesh[];
}
const [selectedElementsMap, setSelectedElementsMap] = useState<Map<number, SelectedElement>>(new Map());
const selectedElementsMapRef = useRef(selectedElementsMap);
useEffect(() => {
selectedPiecesMapRef.current = selectedPiecesMap;
}, [selectedPiecesMap]);
selectedElementsMapRef.current = selectedElementsMap;
}, [selectedElementsMap]);
const [isSelectionModalOpen, setIsSelectionModalOpen] = useState<boolean>(false);
const pointerDownInfoRef = useRef<{ x: number; y: number; time: number }>({ x: 0, y: 0, time: 0 });
@@ -547,8 +553,9 @@ export const ModelViewer3D: React.FC<ModelViewer3DProps> = ({
}
}
// Se a peça estiver marcada na seleção permanente, preserva a cor magenta
if (selectedPiecesMapRef.current.has(mesh.uuid)) {
// Se a peça física pertencer à seleção permanente, preserva a cor magenta
const ifcId = mesh.userData.ifcId;
if (ifcId !== undefined && selectedElementsMapRef.current.has(ifcId)) {
mesh.userData.originalMaterial = mesh.material;
mesh.material = magentaMaterialRef.current;
} else {
@@ -782,25 +789,39 @@ export const ModelViewer3D: React.FC<ModelViewer3DProps> = ({
}
if (hitMesh && hitMesh.visible) {
const nextMap = new Map(selectedPiecesMapRef.current);
const targetIfcId = hitMesh.userData.ifcId;
if (targetIfcId === undefined) return;
if (nextMap.has(hitMesh.uuid)) {
// Desmarcar peça (restaura material original)
if (hitMesh.userData.originalMaterial) {
hitMesh.material = hitMesh.userData.originalMaterial;
}
hitMesh.userData.isMagentaSelected = false;
nextMap.delete(hitMesh.uuid);
const nextMap = new Map(selectedElementsMapRef.current);
if (nextMap.has(targetIfcId)) {
// Desmarcar peça física inteira: restaura todas as suas sub-malhas geométricas
const element = nextMap.get(targetIfcId)!;
element.meshes.forEach((m) => {
if (m.userData.originalMaterial) {
m.material = m.userData.originalMaterial;
}
m.userData.isMagentaSelected = false;
});
nextMap.delete(targetIfcId);
} else {
// Marcar peça em magenta permanente
hitMesh.userData.originalMaterial = hitMesh.material;
hitMesh.userData.isMagentaSelected = true;
hitMesh.material = magentaMaterialRef.current;
// Marcar peça física inteira: localiza e pinta todas as sub-malhas do mesmo ifcId
const relatedMeshes: THREE.Mesh[] = [];
if (modelGroupRef.current) {
modelGroupRef.current.traverse((child) => {
if ((child as THREE.Mesh).isMesh && child.userData.ifcId === targetIfcId) {
const m = child as THREE.Mesh;
m.userData.originalMaterial = m.material;
m.userData.isMagentaSelected = true;
m.material = magentaMaterialRef.current;
relatedMeshes.push(m);
}
});
}
const expressID = hitMesh.userData.ifcId;
const pmark = hitMesh.userData.pieceMark;
const piece: PieceInfo = (modelData && expressID !== undefined && modelData.pieceByExpressID.get(expressID)) || {
expressID: expressID || 0,
const piece: PieceInfo = (modelData && modelData.pieceByExpressID.get(targetIfcId)) || {
expressID: targetIfcId,
guid: '',
name: hitMesh.userData.section || 'Peça',
type: 'PIECE',
@@ -809,16 +830,20 @@ export const ModelViewer3D: React.FC<ModelViewer3DProps> = ({
phase: hitMesh.userData.phase,
};
nextMap.set(hitMesh.uuid, { mesh: hitMesh, piece });
nextMap.set(targetIfcId, {
expressID: targetIfcId,
piece,
meshes: relatedMeshes,
});
onSelectPiece?.(piece);
}
setSelectedPiecesMap(nextMap);
setSelectedElementsMap(nextMap);
}
}
};
// 2 Cliques (Duplo Clique): Seleciona TODAS as peças idênticas no modelo (mesma marca/tag da mesma fase)
// 2 Cliques (Duplo Clique): Seleciona TODAS as peças físicas idênticas no modelo (mesma marca e mesma fase)
const handleDoubleClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (!containerRef.current || !activeCameraRef.current || !modelGroupRef.current || !controlsRef.current) return;
@@ -844,7 +869,10 @@ export const ModelViewer3D: React.FC<ModelViewer3DProps> = ({
const targetPhase = hitMesh.userData.phase ? String(hitMesh.userData.phase).trim() : '';
if (targetPieceMark) {
const nextMap = new Map(selectedPiecesMapRef.current);
const nextMap = new Map(selectedElementsMapRef.current);
// Agrupa todas as sub-malhas por elemento físico (ifcId único)
const meshesByIfcId = new Map<number, { meshes: THREE.Mesh[]; sampleMesh: THREE.Mesh }>();
modelGroupRef.current.traverse((child) => {
if ((child as THREE.Mesh).isMesh && child.visible) {
@@ -852,33 +880,48 @@ export const ModelViewer3D: React.FC<ModelViewer3DProps> = ({
const pmark = String(mesh.userData.pieceMark || '').trim().toUpperCase();
const phase = mesh.userData.phase ? String(mesh.userData.phase).trim() : '';
// Mesma marca e mesma fase (se aplicável)
// Mesma marca e mesma fase
const isMatch = pmark === targetPieceMark && (!targetPhase || !phase || phase === targetPhase);
if (isMatch) {
if (!nextMap.has(mesh.uuid)) {
mesh.userData.originalMaterial = mesh.material;
const ifcId = mesh.userData.ifcId;
if (ifcId !== undefined) {
if (!meshesByIfcId.has(ifcId)) {
meshesByIfcId.set(ifcId, { meshes: [], sampleMesh: mesh });
}
meshesByIfcId.get(ifcId)!.meshes.push(mesh);
}
mesh.userData.isMagentaSelected = true;
mesh.material = magentaMaterialRef.current;
const expressID = mesh.userData.ifcId;
const piece: PieceInfo = (modelData && expressID !== undefined && modelData.pieceByExpressID.get(expressID)) || {
expressID: expressID || 0,
guid: '',
name: mesh.userData.section || 'Peça',
type: 'PIECE',
pieceMark: pmark,
section: mesh.userData.section,
phase: phase || targetPhase,
};
nextMap.set(mesh.uuid, { mesh, piece });
}
}
});
setSelectedPiecesMap(nextMap);
// Pinta e adiciona cada peça física real como 1 unidade
meshesByIfcId.forEach(({ meshes, sampleMesh }, ifcId) => {
meshes.forEach((m) => {
if (!m.userData.isMagentaSelected) {
m.userData.originalMaterial = m.material;
}
m.userData.isMagentaSelected = true;
m.material = magentaMaterialRef.current;
});
const piece: PieceInfo = (modelData && modelData.pieceByExpressID.get(ifcId)) || {
expressID: ifcId,
guid: '',
name: sampleMesh.userData.section || 'Peça',
type: 'PIECE',
pieceMark: sampleMesh.userData.pieceMark,
section: sampleMesh.userData.section,
phase: sampleMesh.userData.phase || targetPhase,
};
nextMap.set(ifcId, {
expressID: ifcId,
piece,
meshes,
});
});
setSelectedElementsMap(nextMap);
}
// Foca suavemente o alvo dos controles na peça clicada
@@ -890,27 +933,30 @@ export const ModelViewer3D: React.FC<ModelViewer3DProps> = ({
// Limpar Seleção Completa
const handleClearSelection = useCallback(() => {
selectedPiecesMapRef.current.forEach(({ mesh }) => {
if (mesh.userData.originalMaterial) {
mesh.material = mesh.userData.originalMaterial;
}
mesh.userData.isMagentaSelected = false;
selectedElementsMapRef.current.forEach(({ meshes }) => {
meshes.forEach((m) => {
if (m.userData.originalMaterial) {
m.material = m.userData.originalMaterial;
}
m.userData.isMagentaSelected = false;
});
});
setSelectedPiecesMap(new Map());
setSelectedElementsMap(new Map());
}, []);
const selectedCount = selectedPiecesMap.size;
// Quantidade real de peças físicas selecionadas na obra
const selectedCount = selectedElementsMap.size;
// Lista consolidada para o modal de seleção
const selectedPiecesList: SelectedPieceItem[] = Array.from(selectedPiecesMap.values()).map(
({ mesh, piece }) => {
const pmark = piece.pieceMark || mesh.userData.pieceMark || 'Sem Marca';
// Lista consolidada de peças físicas para o modal de seleção
const selectedPiecesList: SelectedPieceItem[] = Array.from(selectedElementsMap.values()).map(
({ expressID, piece }) => {
const pmark = piece.pieceMark || 'Sem Marca';
const prod = productionData?.get(pmark);
return {
id: mesh.uuid,
id: String(expressID),
pieceMark: pmark,
phase: piece.phase || mesh.userData.phase,
section: piece.section || mesh.userData.section,
phase: piece.phase,
section: piece.section,
name: piece.name,
pointedQty: prod?.pointedQtd,
totalQty: prod?.totalQtd,