From c9a91ebc7aaa0fa9432d2e5eb564f86db0b35ed4 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 02:47:54 +0000 Subject: [PATCH] Changes --- src/components/ViewerControls.tsx | 38 +++++++++++----- src/components/three/ModelViewer.tsx | 67 +++++++++++++++++++++------- src/stores/useModelStore.ts | 8 +++- 3 files changed, 87 insertions(+), 26 deletions(-) diff --git a/src/components/ViewerControls.tsx b/src/components/ViewerControls.tsx index a70a402..91ff5fd 100644 --- a/src/components/ViewerControls.tsx +++ b/src/components/ViewerControls.tsx @@ -1,4 +1,4 @@ -import { Eye, Grid3X3, Ruler, Trash2, Camera, LayoutGrid, Palette } from 'lucide-react'; +import { Eye, Grid3X3, Ruler, Trash2, Camera, LayoutGrid, Palette, Box } from 'lucide-react'; import { Slider } from '@/components/ui/slider'; import { Button } from '@/components/ui/button'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; @@ -24,6 +24,7 @@ export function ViewerControls() { showGrid, setShowGrid, wireframeColor, setWireframeColor, wireframeThickness, setWireframeThickness, + edgeThresholdAngle, setEdgeThresholdAngle, } = useModelStore(); const handleScreenshot = useCallback(() => { @@ -68,21 +69,24 @@ export function ViewerControls() { Grid - {/* Render mode toggle */} + {/* Render mode cycle: solid -> wireframe -> edges */} setRenderMode(renderMode === 'solid' ? 'wireframe' : 'solid')} + onClick={() => { + const next = renderMode === 'solid' ? 'wireframe' : renderMode === 'wireframe' ? 'edges' : 'solid'; + setRenderMode(next); + }} > - + {renderMode === 'edges' ? : } - {renderMode === 'wireframe' ? 'Wireframe' : 'Sólido'} + {renderMode === 'solid' ? 'Sólido' : renderMode === 'wireframe' ? 'Wireframe' : 'Bordas'} - {/* Wireframe settings popover */} - {renderMode === 'wireframe' && ( + {/* Wireframe/Edges settings popover */} + {renderMode !== 'solid' && ( @@ -92,7 +96,7 @@ export function ViewerControls() { - Cor do Wireframe + Cor das Linhas {WIRE_COLORS.map((c) => ( setWireframeThickness(v)} /> + {renderMode === 'edges' && ( + + + Ângulo Limite + {edgeThresholdAngle}° + + setEdgeThresholdAngle(v)} + /> + + )} )} - {/* Measure tool */} s.renderMode); const wireframeColor = useModelStore((s) => s.wireframeColor); const wireframeThickness = useModelStore((s) => s.wireframeThickness); + const edgeThresholdAngle = useModelStore((s) => s.edgeThresholdAngle); const checklist = useModelStore((s) => s.checklist); const fineTuning = useModelStore((s) => s.fineTuning); @@ -39,6 +40,9 @@ function GLBModel({ url }: { url: string }) { return { size, center }; }, [scene]); + // Store original colors so we can restore them + const originalColors = useRef>(new Map()); + useEffect(() => { const hasRejected = checklist.some(i => i.status === 'rejected'); const allApproved = checklist.every(i => i.status === 'approved'); @@ -50,31 +54,64 @@ function GLBModel({ url }: { url: string }) { } else { child.material = child.material.clone(); } + + // Clean up previous edge lines + const toRemove: THREE.Object3D[] = []; + child.children.forEach(c => { + if (c.userData.__edgeLine) toRemove.push(c); + }); + toRemove.forEach(c => { + if (c instanceof THREE.LineSegments) { + c.geometry.dispose(); + (c.material as THREE.Material).dispose(); + } + child.remove(c); + }); + const materials = Array.isArray(child.material) ? child.material : [child.material]; materials.forEach((mat: THREE.Material) => { if (mat instanceof THREE.MeshStandardMaterial) { - mat.transparent = opacity < 1; - mat.opacity = opacity; - mat.wireframe = renderMode === 'wireframe'; - if (renderMode === 'wireframe') { - mat.wireframeLinewidth = wireframeThickness; + // Store original color on first encounter + if (!originalColors.current.has(mat)) { + originalColors.current.set(mat, mat.color.clone()); } - mat.needsUpdate = true; - mat.needsUpdate = true; - if (renderMode === 'wireframe') { - mat.color.set(wireframeColor); - } else if (hasRejected) { - mat.color.setHSL(0, 0.7, 0.5); - } else if (allApproved) { - mat.color.setHSL(0.38, 0.7, 0.45); + + if (renderMode === 'edges') { + mat.visible = false; } else { - mat.color.set(0x8899aa); + mat.visible = true; + mat.transparent = opacity < 1; + mat.opacity = opacity; + mat.wireframe = renderMode === 'wireframe'; + if (renderMode === 'wireframe') { + mat.wireframeLinewidth = wireframeThickness; + mat.color.set(wireframeColor); + } else if (hasRejected) { + mat.color.setHSL(0, 0.7, 0.5); + } else if (allApproved) { + mat.color.setHSL(0.38, 0.7, 0.45); + } else { + mat.color.set(0x8899aa); + } } + mat.needsUpdate = true; } }); + + // Create edge lines for edges mode + if (renderMode === 'edges' && child.geometry) { + const edgesGeo = new THREE.EdgesGeometry(child.geometry, edgeThresholdAngle); + const lineMat = new THREE.LineBasicMaterial({ + color: wireframeColor, + linewidth: wireframeThickness, + }); + const lineSegments = new THREE.LineSegments(edgesGeo, lineMat); + lineSegments.userData.__edgeLine = true; + child.add(lineSegments); + } } }); - }, [scene, opacity, renderMode, checklist, wireframeColor, wireframeThickness]); + }, [scene, opacity, renderMode, checklist, wireframeColor, wireframeThickness, edgeThresholdAngle]); const rotYRad = (fineTuning.rotY * Math.PI) / 180; diff --git a/src/stores/useModelStore.ts b/src/stores/useModelStore.ts index 9be44cd..2e91ba4 100644 --- a/src/stores/useModelStore.ts +++ b/src/stores/useModelStore.ts @@ -15,7 +15,7 @@ interface FineTuning { type AnchorMode = 'tracking' | 'manual' | 'fine-tuning'; type InspectionResult = 'pending' | 'approved' | 'rejected'; -type RenderMode = 'solid' | 'wireframe'; +type RenderMode = 'solid' | 'wireframe' | 'edges'; export interface MeasurePoint { x: number; @@ -77,6 +77,9 @@ interface ModelStore { wireframeThickness: number; setWireframeThickness: (t: number) => void; + edgeThresholdAngle: number; + setEdgeThresholdAngle: (angle: number) => void; + checklist: ChecklistItem[]; setChecklistItemStatus: (id: string, status: 'approved' | 'rejected') => void; setChecklistItemNote: (id: string, note: string) => void; @@ -136,6 +139,9 @@ export const useModelStore = create((set) => ({ wireframeThickness: 1, setWireframeThickness: (wireframeThickness) => set({ wireframeThickness }), + edgeThresholdAngle: 15, + setEdgeThresholdAngle: (edgeThresholdAngle) => set({ edgeThresholdAngle }), + checklist: defaultChecklist.map(i => ({ ...i })), setChecklistItemStatus: (id, status) => set((state) => ({ checklist: state.checklist.map(item =>