diff --git a/frontend/src/components/viewer/ExamMarkers.tsx b/frontend/src/components/viewer/ExamMarkers.tsx index 7a4db76..f9e965a 100644 --- a/frontend/src/components/viewer/ExamMarkers.tsx +++ b/frontend/src/components/viewer/ExamMarkers.tsx @@ -136,6 +136,14 @@ export function ExamMarkers({ onSelect: (id: string) => void; }) { const { scene } = useThree(); + // As mesmas CameraControls registradas pelo Scene.tsx (makeDefault) — + // acessadas aqui pra enquadrar a câmera na região dos pontos assim que + // os ossos são achados, sem depender/competir com o efeito de + // enquadramento "corpo inteiro" do Scene.tsx (que tem sua própria + // corrida contra o carregamento assíncrono do modelo). + const controls = useThree((state) => state.controls) as unknown as { + fitToBox: (box: THREE.Box3, enableTransition: boolean, opts?: Record) => void; + } | null; const contentBox = useAtlasStore((s) => s.contentBox); const [anchors, setAnchors] = useState({}); @@ -143,17 +151,30 @@ export function ExamMarkers({ let cancelled = false; let attempts = 0; let timer: ReturnType; + let framed = false; const cache = new Map(); function tryCompute() { if (cancelled) return; const boxes = computeAnchorBoxes(scene, cache); setAnchors(boxes); attempts += 1; - // Só continua tentando o que ainda falta (thorax/pelvis/head) — uma - // vez achados, cada Box3 fica em cache e não é recalculado de novo, - // então parar assim que os 3 principais existirem evita trabalho - // repetido (percorrer geometria) rodando junto com o resto da cena. + + // Assim que der pra montar um box com tórax+bacia+crânio, enquadra a + // câmera nele DIRETAMENTE (uma vez só) — garante que a câmera mostre + // exatamente a região onde os pontos de exame estão, não importa o + // que outro efeito de câmera esteja fazendo. const gotCore = boxes.thorax && boxes.pelvis && boxes.head; + if (gotCore && !framed && controls) { + framed = true; + const region = boxes.thorax!.clone().union(boxes.pelvis!).union(boxes.head!); + controls.fitToBox(region, true, { + paddingLeft: 0.3, + paddingRight: 0.3, + paddingTop: 0.2, + paddingBottom: 0.2, + }); + } + if (!gotCore && attempts < 30) { timer = setTimeout(tryCompute, 300); } else if (!boxes.thorax) { @@ -170,7 +191,7 @@ export function ExamMarkers({ cancelled = true; clearTimeout(timer); }; - }, [scene]); + }, [scene, controls]); const techniqueColor = (t: ExamTechnique) => TECHNIQUES.find((x) => x.key === t)?.color ?? "#fff"; const bodyBox = contentBox @@ -199,7 +220,7 @@ export function ExamMarkers({ const isSelected = p.id === selectedId; const color = techniqueColor(p.technique); return ( - +