feat: improve plugin overlay workflow
This commit is contained in:
@@ -10,20 +10,19 @@ import type { Overlay3D } from '@/components/GenericResultView';
|
||||
import { trpc } from '@/lib/trpc';
|
||||
import {
|
||||
LogIn,
|
||||
LogOut,
|
||||
Box,
|
||||
Cloud,
|
||||
Eye,
|
||||
EyeOff,
|
||||
Settings,
|
||||
Maximize2,
|
||||
Crosshair,
|
||||
Train,
|
||||
Loader2,
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export default function Home() {
|
||||
const { user, loading, isAuthenticated, logout } = useAuth();
|
||||
const { loading, isAuthenticated } = useAuth();
|
||||
const utils = trpc.useUtils();
|
||||
|
||||
// ── 树导航选中状态 ─────────────────────────────────────────────────────────
|
||||
@@ -69,7 +68,8 @@ export default function Home() {
|
||||
|
||||
// ── 视图控制 ───────────────────────────────────────────────────────────────
|
||||
const [showHeatmap, setShowHeatmap] = useState(true);
|
||||
const [showCADModel, setShowCADModel] = useState(true);
|
||||
const [showPointCloud, setShowPointCloud] = useState(true);
|
||||
const [showCADModel] = useState(true);
|
||||
const [showWireframe, setShowWireframe] = useState(false);
|
||||
const [pointSize, setPointSize] = useState(0.03);
|
||||
/** 点云预览采样预算(非全量);调低可明显减轻卡顿 */
|
||||
@@ -88,7 +88,7 @@ export default function Home() {
|
||||
resultId: number;
|
||||
overlay: Overlay3D;
|
||||
} | null>(null);
|
||||
const [errorPickEnabled, setErrorPickEnabled] = useState(false);
|
||||
const [errorPickEnabled] = useState(false);
|
||||
const [showLeftPanel, setShowLeftPanel] = useState(true);
|
||||
const [showRightPanel, setShowRightPanel] = useState(true);
|
||||
|
||||
@@ -307,12 +307,47 @@ export default function Home() {
|
||||
setHeatmapViewResultId(resultId);
|
||||
setPointCloudRenderMode('smoothHeatmap');
|
||||
}}
|
||||
onViewPluginOverlay={({ taskId, resultId, overlay }) => {
|
||||
onViewPluginOverlay={({ taskId, resultId, overlay, inputPointCloudFileIds }) => {
|
||||
const expectedIds = inputPointCloudFileIds ?? [];
|
||||
const expectedFiles = expectedIds
|
||||
.map((id) => taskPcFiles.find((f) => f.id === id))
|
||||
.filter((f): f is NonNullable<typeof f> => f != null);
|
||||
const expectedNames = expectedIds
|
||||
.map((id) => taskPcFiles.find((f) => f.id === id)?.name ?? `#${id}`)
|
||||
.join('、');
|
||||
const expectedLabel = expectedFiles.length > 0 && expectedFiles.every((f) => (f as { source?: string }).source === 'registered')
|
||||
? '配准后的点云'
|
||||
: '本算法结果对应的点云文件';
|
||||
const expectedHint = expectedNames ? `${expectedLabel}:${expectedNames}` : expectedLabel;
|
||||
|
||||
if (pointCloudTaskId == null || pointCloudUrls.length === 0) {
|
||||
toast.error(`请先在左侧选择并加载${expectedHint}`);
|
||||
return;
|
||||
}
|
||||
if (pcLoading) {
|
||||
toast.warning('点云仍在加载中,请等待加载完成后再查看叠加层');
|
||||
return;
|
||||
}
|
||||
if (pointCloudTaskId !== taskId) {
|
||||
toast.error(`当前 3D 视口加载的点云不属于该算法结果,请先在左侧选择并加载${expectedHint}`);
|
||||
return;
|
||||
}
|
||||
if (expectedIds.length === 0) {
|
||||
toast.warning('无法确认匹配关系,请加载该结果运行时使用的点云');
|
||||
} else {
|
||||
const loadedIds = selectedPcFileIds.length > 0
|
||||
? selectedPcFileIds
|
||||
: taskPcFiles.map((f) => f.id);
|
||||
const missingIds = expectedIds.filter((id) => !loadedIds.includes(id));
|
||||
if (missingIds.length > 0) {
|
||||
toast.error(`当前加载的点云与该结果不匹配,请在左侧选择并加载${expectedHint}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setHeatmapViewResultId(null);
|
||||
setPluginOverlay({ taskId, resultId, overlay });
|
||||
if (overlay.kind === 'vertexColors') {
|
||||
setPointCloudRenderMode('smoothHeatmap');
|
||||
}
|
||||
setPointCloudRenderMode(overlay.kind === 'vertexColors' ? 'smoothHeatmap' : 'points');
|
||||
}}
|
||||
externalPending={runAnalysisJob.isPending}
|
||||
onRunAssembly={() => {
|
||||
@@ -374,33 +409,22 @@ export default function Home() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 px-3 py-1 industrial-card">
|
||||
<span className={`status-led ${
|
||||
pcLoading ? 'status-led-processing' :
|
||||
cadLoading ? 'status-led-processing' :
|
||||
'status-led-idle'
|
||||
}`} />
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{pcLoading ? `加载点云 ${pcProgress}%` :
|
||||
cadLoading ? `加载 CAD ${cadLoadProgress}%` :
|
||||
'待机'}
|
||||
</span>
|
||||
</div>
|
||||
{(pcLoading || cadLoading) && (
|
||||
<div className="flex items-center gap-2 px-3 py-1 industrial-card">
|
||||
<span className="status-led status-led-processing" />
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{pcLoading ? `加载点云 ${pcProgress}%` : `加载 CAD ${cadLoadProgress}%`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<div className="w-7 h-7 rounded bg-secondary animate-pulse" />
|
||||
) : isAuthenticated ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground">{user?.name || '用户'}</span>
|
||||
<button className="industrial-btn py-1.5" onClick={() => logout()}>
|
||||
<LogOut className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
) : !isAuthenticated ? (
|
||||
<a href={getLoginUrl()} className="industrial-btn industrial-btn-primary py-1.5">
|
||||
<LogIn className="w-3.5 h-3.5" />
|
||||
</a>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -439,6 +463,7 @@ export default function Home() {
|
||||
pointCloudUrls={pointCloudUrlsStable}
|
||||
batchTaskId={batchTaskId ?? undefined}
|
||||
loadKey={pcLoadKey}
|
||||
showPointCloud={showPointCloud}
|
||||
selectedPcFileIds={selectedPcFileIds.length > 0 ? selectedPcFileIds : undefined}
|
||||
onCadProgress={handleCadProgress}
|
||||
onCadLoaded={handleCadLoaded}
|
||||
@@ -474,13 +499,9 @@ export default function Home() {
|
||||
|
||||
{/* 操作提示 */}
|
||||
<div className="absolute bottom-3 left-3 info-overlay flex items-center gap-4 text-[10px] flex-wrap">
|
||||
{errorPickEnabled && pointCloudRenderMode === 'smoothHeatmap' && (
|
||||
<span className="text-amber-400/90">平滑模式不支持选点,请切回「点云」</span>
|
||||
)}
|
||||
{errorPickEnabled && pointCloudRenderMode === 'points'
|
||||
? <><span>左键点击选点误差</span><span>右键平移</span><span>滚轮缩放</span></>
|
||||
: <><span>左键旋转</span><span>右键平移</span><span>滚轮缩放</span></>
|
||||
}
|
||||
<span>左键旋转</span>
|
||||
<span>右键平移</span>
|
||||
<span>滚轮缩放</span>
|
||||
</div>
|
||||
|
||||
{/* 选中点信息 */}
|
||||
@@ -522,15 +543,21 @@ export default function Home() {
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className={`industrial-btn py-1 ${pointCloudRenderMode === 'points' ? 'industrial-btn-primary' : ''}`}
|
||||
className={`industrial-btn h-7 px-2 py-0 inline-flex items-center gap-1 text-[10px] ${showPointCloud ? 'industrial-btn-primary' : ''}`}
|
||||
onClick={() => {
|
||||
setPointCloudRenderMode('points');
|
||||
setHeatmapViewResultId(null);
|
||||
setPluginOverlay(null);
|
||||
if (pointCloudRenderMode !== 'points') {
|
||||
setPointCloudRenderMode('points');
|
||||
setHeatmapViewResultId(null);
|
||||
setPluginOverlay(null);
|
||||
setShowPointCloud(true);
|
||||
return;
|
||||
}
|
||||
setShowPointCloud(v => !v);
|
||||
}}
|
||||
title="逐点显示(支持误差拾取)"
|
||||
title={showPointCloud ? '隐藏点云' : '显示点云'}
|
||||
>
|
||||
<span className="text-[10px]">点云</span>
|
||||
<Cloud className="w-3.5 h-3.5" />
|
||||
<span>点云</span>
|
||||
</button>
|
||||
{heatmapViewResultId != null && pointCloudRenderMode === 'smoothHeatmap' && (
|
||||
<span className="text-[9px] text-primary/90 px-1.5 py-0.5 rounded bg-primary/10 border border-primary/20">
|
||||
@@ -538,26 +565,12 @@ export default function Home() {
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
className={`industrial-btn py-1 ${showHeatmap ? 'industrial-btn-primary' : ''}`}
|
||||
type="button"
|
||||
className={`industrial-btn h-7 px-2 py-0 inline-flex items-center gap-1 text-[10px] ${showHeatmap ? 'industrial-btn-primary' : ''}`}
|
||||
onClick={() => setShowHeatmap(v => !v)}
|
||||
>
|
||||
{showHeatmap ? <Eye className="w-3.5 h-3.5" /> : <EyeOff className="w-3.5 h-3.5" />}
|
||||
<span className="ml-1">热力图</span>
|
||||
</button>
|
||||
<button
|
||||
className={`industrial-btn py-1 ${showCADModel ? 'industrial-btn-primary' : ''}`}
|
||||
onClick={() => setShowCADModel(v => !v)}
|
||||
>
|
||||
<Box className="w-3.5 h-3.5" />
|
||||
<span className="ml-1">CAD</span>
|
||||
</button>
|
||||
<button
|
||||
className={`industrial-btn py-1 ${errorPickEnabled ? 'industrial-btn-primary' : ''}`}
|
||||
onClick={() => setErrorPickEnabled(v => !v)}
|
||||
title="开启后左键点击显示该点误差"
|
||||
>
|
||||
<Crosshair className="w-3.5 h-3.5" />
|
||||
<span className="ml-1">误差拾取</span>
|
||||
<span>热力图</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -584,7 +597,7 @@ export default function Home() {
|
||||
<input
|
||||
type="range"
|
||||
min={100_000}
|
||||
max={5_000_000}
|
||||
max={2_000_000}
|
||||
step={100_000}
|
||||
value={renderPointBudget}
|
||||
onChange={e => setRenderPointBudget(Number(e.target.value))}
|
||||
|
||||
Reference in New Issue
Block a user