🚀 Auto-deploy: melhoria no snap e medição AR em 22/05/2026 21:20:32
This commit is contained in:
@@ -8,6 +8,7 @@ import Viewer from "./pages/Viewer";
|
|||||||
import XRSession from "./pages/XRSession";
|
import XRSession from "./pages/XRSession";
|
||||||
import Watch from "./pages/Watch";
|
import Watch from "./pages/Watch";
|
||||||
import NotFound from "./pages/NotFound";
|
import NotFound from "./pages/NotFound";
|
||||||
|
import "@/lib/remoteLogger";
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ function GLBModel({ sceneModel, isActive }: { sceneModel: SceneModel; isActive:
|
|||||||
const checklist = useModelStore((s) => s.checklist);
|
const checklist = useModelStore((s) => s.checklist);
|
||||||
const setActive = useModelStore((s) => s.setActiveModel);
|
const setActive = useModelStore((s) => s.setActiveModel);
|
||||||
const selectionMode = useModelStore((s) => s.selectionMode);
|
const selectionMode = useModelStore((s) => s.selectionMode);
|
||||||
|
const measureMode = useModelStore((s) => s.measureMode);
|
||||||
const fineTuning = sceneModel.fineTuning;
|
const fineTuning = sceneModel.fineTuning;
|
||||||
|
|
||||||
const modelInfo = useMemo(() => {
|
const modelInfo = useMemo(() => {
|
||||||
@@ -160,12 +161,13 @@ function GLBModel({ sceneModel, isActive }: { sceneModel: SceneModel; isActive:
|
|||||||
originalColors.current.set(mat, mat.color.clone());
|
originalColors.current.set(mat, mat.color.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (renderMode === 'edges') {
|
if (renderMode === 'edges' && !measureMode) {
|
||||||
mat.visible = false;
|
mat.visible = false;
|
||||||
} else {
|
} else {
|
||||||
mat.visible = true;
|
mat.visible = true;
|
||||||
mat.transparent = opacity < 1;
|
const targetOpacity = measureMode ? 0.25 : opacity;
|
||||||
mat.opacity = opacity;
|
mat.transparent = targetOpacity < 1;
|
||||||
|
mat.opacity = targetOpacity;
|
||||||
mat.wireframe = renderMode === 'wireframe';
|
mat.wireframe = renderMode === 'wireframe';
|
||||||
if (renderMode === 'wireframe') {
|
if (renderMode === 'wireframe') {
|
||||||
mat.wireframeLinewidth = wireframeThickness;
|
mat.wireframeLinewidth = wireframeThickness;
|
||||||
@@ -183,11 +185,11 @@ function GLBModel({ sceneModel, isActive }: { sceneModel: SceneModel; isActive:
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (renderMode === 'edges' && child.geometry) {
|
if ((renderMode === 'edges' || measureMode) && child.geometry) {
|
||||||
const edgesGeo = new THREE.EdgesGeometry(child.geometry, edgeThresholdAngle);
|
const edgesGeo = new THREE.EdgesGeometry(child.geometry, edgeThresholdAngle);
|
||||||
const lineMat = new THREE.LineBasicMaterial({
|
const lineMat = new THREE.LineBasicMaterial({
|
||||||
color: wireframeColor,
|
color: measureMode ? '#00f3ff' : wireframeColor,
|
||||||
linewidth: wireframeThickness,
|
linewidth: measureMode ? 2 : wireframeThickness,
|
||||||
});
|
});
|
||||||
const lineSegments = new THREE.LineSegments(edgesGeo, lineMat);
|
const lineSegments = new THREE.LineSegments(edgesGeo, lineMat);
|
||||||
lineSegments.userData.__edgeLine = true;
|
lineSegments.userData.__edgeLine = true;
|
||||||
@@ -195,7 +197,7 @@ function GLBModel({ sceneModel, isActive }: { sceneModel: SceneModel; isActive:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [scene, opacity, renderMode, checklist, wireframeColor, wireframeThickness, edgeThresholdAngle, sceneModel.color]);
|
}, [scene, opacity, renderMode, checklist, wireframeColor, wireframeThickness, edgeThresholdAngle, sceneModel.color, measureMode]);
|
||||||
|
|
||||||
const rotXRad = (fineTuning.rotX * Math.PI) / 180;
|
const rotXRad = (fineTuning.rotX * Math.PI) / 180;
|
||||||
const rotYRad = (fineTuning.rotY * Math.PI) / 180;
|
const rotYRad = (fineTuning.rotY * Math.PI) / 180;
|
||||||
|
|||||||
@@ -69,17 +69,12 @@ export function XRControllerMeasure() {
|
|||||||
// running them every frame at 72fps can stall the Quest right after
|
// running them every frame at 72fps can stall the Quest right after
|
||||||
// toggling "Medir" from the Ferramentas tab. We cap heavy work to ~24Hz.
|
// toggling "Medir" from the Ferramentas tab. We cap heavy work to ~24Hz.
|
||||||
// Trigger/A/B/L-trigger polling continues every frame for responsiveness.
|
// Trigger/A/B/L-trigger polling continues every frame for responsiveness.
|
||||||
const heavyFrame = useRef(0);
|
|
||||||
const cachedSnappedPoint = useRef<THREE.Vector3 | null>(null);
|
|
||||||
const cachedSnapKind = useRef<'vertex' | 'edge' | 'surface' | 'hole'>('surface');
|
|
||||||
|
|
||||||
useFrame((_state, _dt, frame: XRFrame | undefined) => {
|
useFrame((_state, _dt, frame: XRFrame | undefined) => {
|
||||||
const measureMode = useModelStore.getState().measureMode;
|
const measureMode = useModelStore.getState().measureMode;
|
||||||
const selectionMode = useModelStore.getState().selectionMode;
|
const selectionMode = useModelStore.getState().selectionMode;
|
||||||
if (laserRef.current) laserRef.current.visible = false;
|
if (laserRef.current) laserRef.current.visible = false;
|
||||||
if (tipRef.current) tipRef.current.visible = false;
|
if (tipRef.current) tipRef.current.visible = false;
|
||||||
if ((!measureMode && !selectionMode) || !frame) return;
|
if ((!measureMode && !selectionMode) || !frame) return;
|
||||||
const doHeavy = (heavyFrame.current++ % 3) === 0;
|
|
||||||
|
|
||||||
const session = frame.session;
|
const session = frame.session;
|
||||||
const refSpace = gl.xr.getReferenceSpace();
|
const refSpace = gl.xr.getReferenceSpace();
|
||||||
@@ -124,7 +119,6 @@ export function XRControllerMeasure() {
|
|||||||
let snapKind: 'vertex' | 'edge' | 'surface' | 'hole' = 'surface';
|
let snapKind: 'vertex' | 'edge' | 'surface' | 'hole' = 'surface';
|
||||||
let hoverDetected: { kind: 'hole' | 'edge'; value: number; position: THREE.Vector3; endpoints?: { a: THREE.Vector3; b: THREE.Vector3 } } | null = null;
|
let hoverDetected: { kind: 'hole' | 'edge'; value: number; position: THREE.Vector3; endpoints?: { a: THREE.Vector3; b: THREE.Vector3 } } | null = null;
|
||||||
|
|
||||||
if (doHeavy) {
|
|
||||||
const hits = raycaster.current.intersectObjects(scene.children, true);
|
const hits = raycaster.current.intersectObjects(scene.children, true);
|
||||||
const hit = hits.find((h) => {
|
const hit = hits.find((h) => {
|
||||||
const o = h.object;
|
const o = h.object;
|
||||||
@@ -137,7 +131,11 @@ export function XRControllerMeasure() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (hit && hit.object instanceof THREE.Mesh) {
|
if (hit && hit.object instanceof THREE.Mesh) {
|
||||||
// Snap to existing registered hole centers first (within ~4cm)
|
const dist = tmpOrigin.current.distanceTo(hit.point);
|
||||||
|
const vertexThreshold = Math.max(0.06, dist * 0.08);
|
||||||
|
const edgeThreshold = Math.max(0.08, dist * 0.10);
|
||||||
|
|
||||||
|
// Snap to existing registered hole centers first
|
||||||
const existing = useModelStore.getState().measurements;
|
const existing = useModelStore.getState().measurements;
|
||||||
let bestHoleCenter: THREE.Vector3 | null = null;
|
let bestHoleCenter: THREE.Vector3 | null = null;
|
||||||
let bestHoleDist = Infinity;
|
let bestHoleDist = Infinity;
|
||||||
@@ -145,7 +143,7 @@ export function XRControllerMeasure() {
|
|||||||
if (m.kind !== 'hole') continue;
|
if (m.kind !== 'hole') continue;
|
||||||
const c = new THREE.Vector3(m.pointA.x, m.pointA.y, m.pointA.z);
|
const c = new THREE.Vector3(m.pointA.x, m.pointA.y, m.pointA.z);
|
||||||
const d = hit.point.distanceTo(c);
|
const d = hit.point.distanceTo(c);
|
||||||
if (d < 0.04 && d < bestHoleDist) {
|
if (d < 0.05 && d < bestHoleDist) {
|
||||||
bestHoleDist = d;
|
bestHoleDist = d;
|
||||||
bestHoleCenter = c;
|
bestHoleCenter = c;
|
||||||
}
|
}
|
||||||
@@ -155,19 +153,17 @@ export function XRControllerMeasure() {
|
|||||||
snappedPoint = bestHoleCenter;
|
snappedPoint = bestHoleCenter;
|
||||||
snapKind = 'hole';
|
snapKind = 'hole';
|
||||||
} else if (snapEnabled) {
|
} else if (snapEnabled) {
|
||||||
// Try detecting a circular edge (hole) at hit (8cm radius)
|
const circle = detectCircularEdgeAtPoint3D(hit.object, hit.point, Math.max(0.08, dist * 0.12));
|
||||||
const circle = detectCircularEdgeAtPoint3D(hit.object, hit.point, 0.08);
|
|
||||||
if (circle) {
|
if (circle) {
|
||||||
snappedPoint = circle.center;
|
snappedPoint = circle.center;
|
||||||
snapKind = 'hole';
|
snapKind = 'hole';
|
||||||
hoverDetected = { kind: 'hole', value: circle.diameterMM, position: circle.center };
|
hoverDetected = { kind: 'hole', value: circle.diameterMM, position: circle.center };
|
||||||
} else {
|
} else {
|
||||||
// Snap 3D físico puro (7cm para vértices, 9cm para arestas)
|
const snap = resolveSnap3D(hit.object, hit.point, vertexThreshold, edgeThreshold);
|
||||||
const snap = resolveSnap3D(hit.object, hit.point, 0.07, 0.09);
|
|
||||||
snappedPoint = snap.point;
|
snappedPoint = snap.point;
|
||||||
snapKind = snap.type;
|
snapKind = snap.type;
|
||||||
if (snap.type === 'edge') {
|
if (snap.type === 'edge') {
|
||||||
const seg = findNearestEdgeSegment3D(hit.object, hit.point, 0.09);
|
const seg = findNearestEdgeSegment3D(hit.object, hit.point, edgeThreshold);
|
||||||
if (seg) {
|
if (seg) {
|
||||||
const lenMM = seg.a.distanceTo(seg.b) * 1000;
|
const lenMM = seg.a.distanceTo(seg.b) * 1000;
|
||||||
hoverDetected = { kind: 'edge', value: lenMM, position: seg.midpoint, endpoints: { a: seg.a, b: seg.b } };
|
hoverDetected = { kind: 'edge', value: lenMM, position: seg.midpoint, endpoints: { a: seg.a, b: seg.b } };
|
||||||
@@ -179,16 +175,6 @@ export function XRControllerMeasure() {
|
|||||||
snapKind = 'surface';
|
snapKind = 'surface';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Cache for non-heavy frames so the laser + trigger still have a target.
|
|
||||||
// Sticky: se conseguimos um snap "forte" (vertex/edge/hole), preservamos
|
|
||||||
// ele entre frames leves para evitar cintilação.
|
|
||||||
cachedSnappedPoint.current = snappedPoint ? snappedPoint.clone() : null;
|
|
||||||
cachedSnapKind.current = snapKind;
|
|
||||||
} else {
|
|
||||||
// Light frame: reuse the last computed snap so visuals stay smooth
|
|
||||||
snappedPoint = cachedSnappedPoint.current ? cachedSnappedPoint.current.clone() : null;
|
|
||||||
snapKind = cachedSnapKind.current;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Dwell detection (1 s) to auto-register hovered hole/edge ─────
|
// ── Dwell detection (1 s) to auto-register hovered hole/edge ─────
|
||||||
const measureModeNow = useModelStore.getState().measureMode;
|
const measureModeNow = useModelStore.getState().measureMode;
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
|
||||||
|
const logChannel = supabase.channel('global-app-logs', {
|
||||||
|
config: { broadcast: { self: true, ack: false } },
|
||||||
|
});
|
||||||
|
|
||||||
|
logChannel.subscribe();
|
||||||
|
|
||||||
|
export function sendRemoteLog(level: 'info' | 'warn' | 'error', message: string, data?: any) {
|
||||||
|
console.log(`[RemoteLog] [${level}] ${message}`, data || '');
|
||||||
|
logChannel.send({
|
||||||
|
type: 'broadcast',
|
||||||
|
event: 'log',
|
||||||
|
payload: {
|
||||||
|
level,
|
||||||
|
message,
|
||||||
|
data: data ? JSON.parse(JSON.stringify(data)) : null,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
userAgent: navigator.userAgent,
|
||||||
|
},
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('Falha ao enviar log remoto:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Captura global de erros em tempo de execução
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.addEventListener('error', (event) => {
|
||||||
|
sendRemoteLog('error', `Global error: ${event.message} at ${event.filename}:${event.lineno}`, {
|
||||||
|
colno: event.colno,
|
||||||
|
stack: event.error?.stack,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('unhandledrejection', (event) => {
|
||||||
|
sendRemoteLog('error', `Unhandled Promise rejection: ${event.reason}`, {
|
||||||
|
reason: event.reason?.stack || event.reason,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { Upload, Glasses, CheckCircle, XCircle, Loader2, Box, Package } from 'lu
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useModelStore } from '@/stores/useModelStore';
|
import { useModelStore } from '@/stores/useModelStore';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
import { getSupportedExtension, convertToGLB, ACCEPTED_EXTENSIONS } from '@/lib/convertToGLB';
|
import { getSupportedExtension, convertToGLB, ACCEPTED_EXTENSIONS } from '@/lib/convertToGLB';
|
||||||
import { convertIFCtoGLB } from '@/lib/convertIFC';
|
import { convertIFCtoGLB } from '@/lib/convertIFC';
|
||||||
import { validateModelFile } from '@/lib/validateModelFile';
|
import { validateModelFile } from '@/lib/validateModelFile';
|
||||||
@@ -23,6 +24,22 @@ const Index = () => {
|
|||||||
const [converting, setConverting] = useState(false);
|
const [converting, setConverting] = useState(false);
|
||||||
const { model, addModel, models, maxModels, xrSupported, setXrSupported } = useModelStore();
|
const { model, addModel, models, maxModels, xrSupported, setXrSupported } = useModelStore();
|
||||||
|
|
||||||
|
// --- Painel de Logs Remotos em Tempo Real ---
|
||||||
|
const [debugLogs, setDebugLogs] = useState<{ level: 'info' | 'warn' | 'error'; message: string; timestamp: string; data?: any }[]>([]);
|
||||||
|
const [showLogs, setShowLogs] = useState(() => new URLSearchParams(window.location.search).get('viewlogs') === 'true');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showLogs) return;
|
||||||
|
const logChannel = supabase.channel('global-app-logs-viewer');
|
||||||
|
logChannel.on('broadcast', { event: 'log' }, ({ payload }) => {
|
||||||
|
setDebugLogs(prev => [payload, ...prev].slice(0, 100));
|
||||||
|
}).subscribe();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
logChannel.unsubscribe();
|
||||||
|
};
|
||||||
|
}, [showLogs]);
|
||||||
|
|
||||||
const [ifcConvert, setIfcConvert] = useState(() => {
|
const [ifcConvert, setIfcConvert] = useState(() => {
|
||||||
const saved = localStorage.getItem('tsxr_ifc_convert');
|
const saved = localStorage.getItem('tsxr_ifc_convert');
|
||||||
return saved !== 'false';
|
return saved !== 'false';
|
||||||
@@ -312,6 +329,49 @@ const Index = () => {
|
|||||||
<p className="mt-16 text-center font-mono text-xs text-muted-foreground/50">
|
<p className="mt-16 text-center font-mono text-xs text-muted-foreground/50">
|
||||||
TrackSteelXR v1.04 — Q.C. Inspection
|
TrackSteelXR v1.04 — Q.C. Inspection
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{showLogs && (
|
||||||
|
<div className="fixed bottom-4 right-4 z-50 flex h-[400px] w-[500px] flex-col rounded-lg border border-border bg-slate-950/95 p-4 shadow-2xl backdrop-blur font-mono text-xs text-slate-200">
|
||||||
|
<div className="flex items-center justify-between border-b border-border pb-2 mb-2">
|
||||||
|
<span className="font-bold text-primary flex items-center gap-2">
|
||||||
|
<span className="h-2 w-2 rounded-full bg-emerald-500 animate-ping" />
|
||||||
|
Console de Logs Remotos (Quest 3)
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button size="sm" variant="outline" className="h-6 px-2 text-[10px]" onClick={() => setDebugLogs([])}>
|
||||||
|
Limpar
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" variant="outline" className="h-6 px-2 text-[10px] text-destructive" onClick={() => setShowLogs(false)}>
|
||||||
|
Fechar
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 overflow-y-auto space-y-2 pr-1">
|
||||||
|
{debugLogs.length === 0 ? (
|
||||||
|
<p className="text-slate-500 text-center mt-12">Aguardando logs do headset Quest 3...</p>
|
||||||
|
) : (
|
||||||
|
debugLogs.map((log, i) => (
|
||||||
|
<div key={i} className={`p-2 rounded border ${
|
||||||
|
log.level === 'error' ? 'bg-red-950/40 border-red-500/30 text-red-300' :
|
||||||
|
log.level === 'warn' ? 'bg-amber-950/40 border-amber-500/30 text-amber-300' :
|
||||||
|
'bg-slate-900/60 border-slate-700/30 text-slate-300'
|
||||||
|
}`}>
|
||||||
|
<div className="flex justify-between text-[9px] text-slate-400 mb-1">
|
||||||
|
<span>{new Date(log.timestamp).toLocaleTimeString()}</span>
|
||||||
|
<span className="uppercase font-bold">{log.level}</span>
|
||||||
|
</div>
|
||||||
|
<p className="break-all whitespace-pre-wrap">{log.message}</p>
|
||||||
|
{log.data && (
|
||||||
|
<pre className="mt-1 text-[10px] bg-black/40 p-1 rounded overflow-x-auto max-h-24 text-slate-400">
|
||||||
|
{JSON.stringify(log.data, null, 2)}
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>);
|
</div>);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
+20
-6
@@ -23,6 +23,7 @@ import { useDevKit } from '@/devkit/useDevKit';
|
|||||||
import { DevPanel } from '@/devkit/DevPanel';
|
import { DevPanel } from '@/devkit/DevPanel';
|
||||||
import { FakeControllers } from '@/devkit/FakeControllers';
|
import { FakeControllers } from '@/devkit/FakeControllers';
|
||||||
import { VisibilityApplier } from '@/components/three/ModelViewer';
|
import { VisibilityApplier } from '@/components/three/ModelViewer';
|
||||||
|
import { registerModelLocalGroup, unregisterModelLocalGroup } from '@/lib/modelTransforms';
|
||||||
import { parseIFCtoThree } from '@/lib/convertIFC';
|
import { parseIFCtoThree } from '@/lib/convertIFC';
|
||||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||||
|
|
||||||
@@ -99,6 +100,13 @@ function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').
|
|||||||
}, [sceneModel.url, sceneModel.fileName]);
|
}, [sceneModel.url, sceneModel.fileName]);
|
||||||
|
|
||||||
const ref = useRef<THREE.Group>(null);
|
const ref = useRef<THREE.Group>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
const g = ref.current;
|
||||||
|
if (!g) return;
|
||||||
|
registerModelLocalGroup(sceneModel.id, g);
|
||||||
|
return () => unregisterModelLocalGroup(sceneModel.id, g);
|
||||||
|
}, [sceneModel.id]);
|
||||||
|
|
||||||
const fineTuning = sceneModel.fineTuning;
|
const fineTuning = sceneModel.fineTuning;
|
||||||
const opacity = useModelStore((s) => s.opacity);
|
const opacity = useModelStore((s) => s.opacity);
|
||||||
const renderMode = useModelStore((s) => s.renderMode);
|
const renderMode = useModelStore((s) => s.renderMode);
|
||||||
@@ -106,6 +114,7 @@ function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').
|
|||||||
const wireframeThickness = useModelStore((s) => s.wireframeThickness);
|
const wireframeThickness = useModelStore((s) => s.wireframeThickness);
|
||||||
const edgeThresholdAngle = useModelStore((s) => s.edgeThresholdAngle);
|
const edgeThresholdAngle = useModelStore((s) => s.edgeThresholdAngle);
|
||||||
const checklist = useModelStore((s) => s.checklist);
|
const checklist = useModelStore((s) => s.checklist);
|
||||||
|
const measureMode = useModelStore((s) => s.measureMode);
|
||||||
|
|
||||||
const modelInfo = useMemo(() => {
|
const modelInfo = useMemo(() => {
|
||||||
if (!scene) return { size: new THREE.Vector3(), center: new THREE.Vector3() };
|
if (!scene) return { size: new THREE.Vector3(), center: new THREE.Vector3() };
|
||||||
@@ -145,12 +154,13 @@ function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').
|
|||||||
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) {
|
||||||
if (renderMode === 'edges') {
|
if (renderMode === 'edges' && !measureMode) {
|
||||||
mat.visible = false;
|
mat.visible = false;
|
||||||
} else {
|
} else {
|
||||||
mat.visible = true;
|
mat.visible = true;
|
||||||
mat.transparent = opacity < 1;
|
const targetOpacity = measureMode ? 0.25 : opacity;
|
||||||
mat.opacity = opacity;
|
mat.transparent = targetOpacity < 1;
|
||||||
|
mat.opacity = targetOpacity;
|
||||||
mat.wireframe = renderMode === 'wireframe';
|
mat.wireframe = renderMode === 'wireframe';
|
||||||
if (renderMode === 'wireframe') {
|
if (renderMode === 'wireframe') {
|
||||||
mat.wireframeLinewidth = wireframeThickness;
|
mat.wireframeLinewidth = wireframeThickness;
|
||||||
@@ -167,16 +177,19 @@ function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (renderMode === 'edges' && child.geometry) {
|
if ((renderMode === 'edges' || measureMode) && child.geometry) {
|
||||||
const edgesGeo = new THREE.EdgesGeometry(child.geometry, edgeThresholdAngle);
|
const edgesGeo = new THREE.EdgesGeometry(child.geometry, edgeThresholdAngle);
|
||||||
const lineMat = new THREE.LineBasicMaterial({ color: wireframeColor, linewidth: wireframeThickness });
|
const lineMat = new THREE.LineBasicMaterial({
|
||||||
|
color: measureMode ? '#00f3ff' : wireframeColor,
|
||||||
|
linewidth: measureMode ? 2 : wireframeThickness,
|
||||||
|
});
|
||||||
const lineSegments = new THREE.LineSegments(edgesGeo, lineMat);
|
const lineSegments = new THREE.LineSegments(edgesGeo, lineMat);
|
||||||
lineSegments.userData.__edgeLine = true;
|
lineSegments.userData.__edgeLine = true;
|
||||||
child.add(lineSegments);
|
child.add(lineSegments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [scene, opacity, renderMode, checklist, wireframeColor, wireframeThickness, edgeThresholdAngle, sceneModel.color]);
|
}, [scene, opacity, renderMode, checklist, wireframeColor, wireframeThickness, edgeThresholdAngle, sceneModel.color, measureMode]);
|
||||||
|
|
||||||
const rotXRad = (fineTuning.rotX * Math.PI) / 180;
|
const rotXRad = (fineTuning.rotX * Math.PI) / 180;
|
||||||
const rotYRad = (fineTuning.rotY * Math.PI) / 180;
|
const rotYRad = (fineTuning.rotY * Math.PI) / 180;
|
||||||
@@ -457,6 +470,7 @@ function XRSnapHandler() {
|
|||||||
// Click to measure
|
// Click to measure
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!measureMode) return;
|
if (!measureMode) return;
|
||||||
|
if (gl.xr.isPresenting) return; // Ignora cliques simulados do DOM quando o WebXR está ativo
|
||||||
const onClick = (e: MouseEvent) => {
|
const onClick = (e: MouseEvent) => {
|
||||||
if (snapPoint) {
|
if (snapPoint) {
|
||||||
addMeasurePoint({ x: snapPoint.x, y: snapPoint.y, z: snapPoint.z });
|
addMeasurePoint({ x: snapPoint.x, y: snapPoint.y, z: snapPoint.z });
|
||||||
|
|||||||
Reference in New Issue
Block a user