Changes
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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 =>
|
||||||
|
|||||||
Reference in New Issue
Block a user