This commit is contained in:
gpt-engineer-app[bot]
2026-02-27 02:47:54 +00:00
parent 9d9018d83a
commit c9a91ebc7a
3 changed files with 87 additions and 26 deletions
+28 -10
View File
@@ -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 { Slider } from '@/components/ui/slider';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
@@ -24,6 +24,7 @@ export function ViewerControls() {
showGrid, setShowGrid, showGrid, setShowGrid,
wireframeColor, setWireframeColor, wireframeColor, setWireframeColor,
wireframeThickness, setWireframeThickness, wireframeThickness, setWireframeThickness,
edgeThresholdAngle, setEdgeThresholdAngle,
} = useModelStore(); } = useModelStore();
const handleScreenshot = useCallback(() => { const handleScreenshot = useCallback(() => {
@@ -68,21 +69,24 @@ export function ViewerControls() {
<span className="font-mono text-xs">Grid</span> <span className="font-mono text-xs">Grid</span>
</Button> </Button>
{/* Render mode toggle */} {/* Render mode cycle: solid -> wireframe -> edges */}
<Button <Button
variant={renderMode === 'wireframe' ? 'default' : 'outline'} variant={renderMode !== 'solid' ? 'default' : 'outline'}
size="sm" size="sm"
className="gap-2 h-9" className="gap-2 h-9"
onClick={() => setRenderMode(renderMode === 'solid' ? 'wireframe' : 'solid')} onClick={() => {
const next = renderMode === 'solid' ? 'wireframe' : renderMode === 'wireframe' ? 'edges' : 'solid';
setRenderMode(next);
}}
> >
<Grid3X3 className="h-3.5 w-3.5" /> {renderMode === 'edges' ? <Box className="h-3.5 w-3.5" /> : <Grid3X3 className="h-3.5 w-3.5" />}
<span className="font-mono text-xs"> <span className="font-mono text-xs">
{renderMode === 'wireframe' ? 'Wireframe' : 'Sólido'} {renderMode === 'solid' ? 'Sólido' : renderMode === 'wireframe' ? 'Wireframe' : 'Bordas'}
</span> </span>
</Button> </Button>
{/* Wireframe settings popover */} {/* Wireframe/Edges settings popover */}
{renderMode === 'wireframe' && ( {renderMode !== 'solid' && (
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button variant="outline" size="sm" className="gap-2 h-9"> <Button variant="outline" size="sm" className="gap-2 h-9">
@@ -92,7 +96,7 @@ export function ViewerControls() {
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-56 space-y-3" side="top" align="start"> <PopoverContent className="w-56 space-y-3" side="top" align="start">
<div> <div>
<span className="font-mono text-xs text-muted-foreground">Cor do Wireframe</span> <span className="font-mono text-xs text-muted-foreground">Cor das Linhas</span>
<div className="mt-2 flex flex-wrap gap-2"> <div className="mt-2 flex flex-wrap gap-2">
{WIRE_COLORS.map((c) => ( {WIRE_COLORS.map((c) => (
<button <button
@@ -118,10 +122,24 @@ export function ViewerControls() {
onValueChange={([v]) => setWireframeThickness(v)} onValueChange={([v]) => setWireframeThickness(v)}
/> />
</div> </div>
{renderMode === 'edges' && (
<div>
<div className="flex items-center justify-between mb-1">
<span className="font-mono text-xs text-muted-foreground">Ângulo Limite</span>
<span className="font-mono text-xs text-foreground">{edgeThresholdAngle}°</span>
</div>
<Slider
value={[edgeThresholdAngle]}
min={1}
max={45}
step={1}
onValueChange={([v]) => setEdgeThresholdAngle(v)}
/>
</div>
)}
</PopoverContent> </PopoverContent>
</Popover> </Popover>
)} )}
{/* Measure tool */} {/* Measure tool */}
<div className="flex items-end gap-1.5"> <div className="flex items-end gap-1.5">
<Button <Button
+52 -15
View File
@@ -27,6 +27,7 @@ function GLBModel({ url }: { url: string }) {
const renderMode = useModelStore((s) => s.renderMode); const renderMode = useModelStore((s) => s.renderMode);
const wireframeColor = useModelStore((s) => s.wireframeColor); const wireframeColor = useModelStore((s) => s.wireframeColor);
const wireframeThickness = useModelStore((s) => s.wireframeThickness); const wireframeThickness = useModelStore((s) => s.wireframeThickness);
const edgeThresholdAngle = useModelStore((s) => s.edgeThresholdAngle);
const checklist = useModelStore((s) => s.checklist); const checklist = useModelStore((s) => s.checklist);
const fineTuning = useModelStore((s) => s.fineTuning); const fineTuning = useModelStore((s) => s.fineTuning);
@@ -39,6 +40,9 @@ function GLBModel({ url }: { url: string }) {
return { size, center }; return { size, center };
}, [scene]); }, [scene]);
// Store original colors so we can restore them
const originalColors = useRef<Map<THREE.Material, THREE.Color>>(new Map());
useEffect(() => { useEffect(() => {
const hasRejected = checklist.some(i => i.status === 'rejected'); const hasRejected = checklist.some(i => i.status === 'rejected');
const allApproved = checklist.every(i => i.status === 'approved'); const allApproved = checklist.every(i => i.status === 'approved');
@@ -50,31 +54,64 @@ function GLBModel({ url }: { url: string }) {
} else { } else {
child.material = child.material.clone(); 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]; const materials = Array.isArray(child.material) ? child.material : [child.material];
materials.forEach((mat: THREE.Material) => { materials.forEach((mat: THREE.Material) => {
if (mat instanceof THREE.MeshStandardMaterial) { if (mat instanceof THREE.MeshStandardMaterial) {
mat.transparent = opacity < 1; // Store original color on first encounter
mat.opacity = opacity; if (!originalColors.current.has(mat)) {
mat.wireframe = renderMode === 'wireframe'; originalColors.current.set(mat, mat.color.clone());
if (renderMode === 'wireframe') {
mat.wireframeLinewidth = wireframeThickness;
} }
mat.needsUpdate = true;
mat.needsUpdate = true; if (renderMode === 'edges') {
if (renderMode === 'wireframe') { mat.visible = false;
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 { } 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; const rotYRad = (fineTuning.rotY * Math.PI) / 180;
+7 -1
View File
@@ -15,7 +15,7 @@ interface FineTuning {
type AnchorMode = 'tracking' | 'manual' | 'fine-tuning'; type AnchorMode = 'tracking' | 'manual' | 'fine-tuning';
type InspectionResult = 'pending' | 'approved' | 'rejected'; type InspectionResult = 'pending' | 'approved' | 'rejected';
type RenderMode = 'solid' | 'wireframe'; type RenderMode = 'solid' | 'wireframe' | 'edges';
export interface MeasurePoint { export interface MeasurePoint {
x: number; x: number;
@@ -77,6 +77,9 @@ interface ModelStore {
wireframeThickness: number; wireframeThickness: number;
setWireframeThickness: (t: number) => void; setWireframeThickness: (t: number) => void;
edgeThresholdAngle: number;
setEdgeThresholdAngle: (angle: number) => void;
checklist: ChecklistItem[]; checklist: ChecklistItem[];
setChecklistItemStatus: (id: string, status: 'approved' | 'rejected') => void; setChecklistItemStatus: (id: string, status: 'approved' | 'rejected') => void;
setChecklistItemNote: (id: string, note: string) => void; setChecklistItemNote: (id: string, note: string) => void;
@@ -136,6 +139,9 @@ export const useModelStore = create<ModelStore>((set) => ({
wireframeThickness: 1, wireframeThickness: 1,
setWireframeThickness: (wireframeThickness) => set({ wireframeThickness }), setWireframeThickness: (wireframeThickness) => set({ wireframeThickness }),
edgeThresholdAngle: 15,
setEdgeThresholdAngle: (edgeThresholdAngle) => set({ edgeThresholdAngle }),
checklist: defaultChecklist.map(i => ({ ...i })), checklist: defaultChecklist.map(i => ({ ...i })),
setChecklistItemStatus: (id, status) => set((state) => ({ setChecklistItemStatus: (id, status) => set((state) => ({
checklist: state.checklist.map(item => checklist: state.checklist.map(item =>