2026-05-21-09-29-47 修复自动拉伸基准与缩放步长
This commit is contained in:
@@ -107,10 +107,28 @@ const modelPoseLimits: Record<ModelPoseKey, { min: number; max: number }> = {
|
||||
translateZ: { min: -2, max: 2 },
|
||||
scale: { min: 0.5, max: 2.5 },
|
||||
};
|
||||
const modelPoseStepPrecision: Partial<Record<ModelPoseKey, number>> = {
|
||||
scale: 3,
|
||||
};
|
||||
|
||||
function clampModelPoseValue(key: ModelPoseKey, value: number) {
|
||||
const limit = modelPoseLimits[key];
|
||||
return Math.max(limit.min, Math.min(limit.max, value));
|
||||
const clampedValue = Math.max(limit.min, Math.min(limit.max, value));
|
||||
const precision = modelPoseStepPrecision[key];
|
||||
return typeof precision === 'number' ? Number(clampedValue.toFixed(precision)) : clampedValue;
|
||||
}
|
||||
|
||||
function getControlStepPrecision(step: number) {
|
||||
if (step >= 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const text = step.toString();
|
||||
if (text.includes('e-')) {
|
||||
return Number(text.split('e-')[1] ?? 2);
|
||||
}
|
||||
|
||||
return text.split('.')[1]?.length ?? 0;
|
||||
}
|
||||
|
||||
function clampModelPose(next: ModelPose): ModelPose {
|
||||
@@ -1806,7 +1824,7 @@ export default function ProjectLibrary({
|
||||
{ key: 'translateX' as const, label: '平移 X', min: -2, max: 2, step: 0.05, value: modelPose.translateX, minus: '-X', plus: '+X', delta: 0.25 },
|
||||
{ key: 'translateY' as const, label: '平移 Y', min: -2, max: 2, step: 0.05, value: modelPose.translateY, minus: '-Y', plus: '+Y', delta: 0.25 },
|
||||
{ key: 'translateZ' as const, label: '平移 Z', min: -2, max: 2, step: 0.05, value: modelPose.translateZ, minus: '-Z', plus: '+Z', delta: 0.25 },
|
||||
{ key: 'scale' as const, label: '缩放', min: 0.5, max: 2.5, step: 0.05, value: modelPose.scale, minus: '-0.1', plus: '+0.1', delta: 0.1 },
|
||||
{ key: 'scale' as const, label: '缩放', min: 0.5, max: 2.5, step: 0.005, value: modelPose.scale, minus: '-0.005', plus: '+0.005', delta: 0.005 },
|
||||
].map((item) => (
|
||||
<div key={item.key} className="grid grid-cols-[48px_40px_1fr_40px_42px] items-center gap-2">
|
||||
<span className="text-[10px] font-bold text-slate-500">{item.label}</span>
|
||||
@@ -1833,7 +1851,7 @@ export default function ProjectLibrary({
|
||||
>
|
||||
{item.plus}
|
||||
</button>
|
||||
<span className="text-[10px] font-mono text-slate-400 text-right">{Number(item.value).toFixed(item.step < 1 ? 2 : 0)}</span>
|
||||
<span className="text-[10px] font-mono text-slate-400 text-right">{Number(item.value).toFixed(getControlStepPrecision(item.step))}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -80,7 +80,7 @@ const poseStepConfig: Record<ModelPoseKey, { min: number; max: number; step: num
|
||||
translateX: { min: -2, max: 2, step: 0.005, minus: '-X', plus: '+X' },
|
||||
translateY: { min: -2, max: 2, step: 0.005, minus: '-Y', plus: '+Y' },
|
||||
translateZ: { min: -2, max: 2, step: 0.005, minus: '-Z', plus: '+Z' },
|
||||
scale: { min: 0.5, max: 3, step: 0.05, minus: '-S', plus: '+S' },
|
||||
scale: { min: 0.5, max: 3, step: 0.005, minus: '-S', plus: '+S' },
|
||||
};
|
||||
|
||||
const defaultModelPose: ModelPose = {
|
||||
@@ -2585,19 +2585,19 @@ export default function ReverseWorkspace({
|
||||
return volumePayload;
|
||||
};
|
||||
|
||||
const loadVisibleModelBounds = async () => {
|
||||
const loadGlobalModelBounds = async () => {
|
||||
if (!project) {
|
||||
return null;
|
||||
}
|
||||
const visibleFiles = (project.stlFiles ?? []).filter((fileName) => moduleStyles[fileName]?.visible !== false);
|
||||
const cacheKey = `${project.id}:${visibleFiles.join('|')}`;
|
||||
const modelFiles = project.stlFiles ?? [];
|
||||
const cacheKey = `${project.id}:global:${modelFiles.join('|')}`;
|
||||
const cached = modelBoundsCacheRef.current.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const modelBox = new THREE.Box3();
|
||||
const results = await Promise.allSettled(visibleFiles.map((fileName) => (
|
||||
const results = await Promise.allSettled(modelFiles.map((fileName) => (
|
||||
getCachedModelPreview(project.id, fileName, 1000)
|
||||
)));
|
||||
results.forEach((result) => {
|
||||
@@ -2630,9 +2630,9 @@ export default function ReverseWorkspace({
|
||||
setStretchingAxis(axis);
|
||||
setFusionError('');
|
||||
try {
|
||||
const bounds = await loadVisibleModelBounds();
|
||||
const bounds = await loadGlobalModelBounds();
|
||||
if (!bounds) {
|
||||
throw new Error('未获取到可见 STL 构件边界');
|
||||
throw new Error('未获取到 STL 构件边界');
|
||||
}
|
||||
const rawSize = new THREE.Vector3().subVectors(bounds.max, bounds.min);
|
||||
const rotatedSize = getRotatedModelSize(bounds, modelPose);
|
||||
@@ -2651,10 +2651,21 @@ export default function ReverseWorkspace({
|
||||
};
|
||||
const baseScale = (Math.max(dicomSize.x, dicomSize.y, dicomSize.z) / maxModelSize) * 0.92;
|
||||
const rotatedAxisSize = Math.max(rotatedSize[axis], 1e-6);
|
||||
const nextScale = clampPoseValue('scale', dicomSize[axis] / (rotatedAxisSize * baseScale));
|
||||
const axisFitScale = dicomSize[axis] / (rotatedAxisSize * baseScale);
|
||||
const containmentScale = Math.min(
|
||||
dicomSize.x / (Math.max(rotatedSize.x, 1e-6) * baseScale),
|
||||
dicomSize.y / (Math.max(rotatedSize.y, 1e-6) * baseScale),
|
||||
dicomSize.z / (Math.max(rotatedSize.z, 1e-6) * baseScale),
|
||||
);
|
||||
const limitedByVolume = axisFitScale > containmentScale + 1e-6;
|
||||
const nextScale = clampPoseValue('scale', Math.min(axisFitScale, containmentScale));
|
||||
const nextPose = { ...modelPose, scale: nextScale };
|
||||
updateModelPose({ scale: nextScale }, { markCustom: !options.silentInitial, keepStatus: true });
|
||||
setPoseImportStatus(`已按 ${axis.toUpperCase()} 方向进行三维等比例拉伸`);
|
||||
setPoseImportStatus(
|
||||
limitedByVolume
|
||||
? `已按 ${axis.toUpperCase()} 方向拉伸,并限制在 DICOM 体范围内`
|
||||
: `已按 ${axis.toUpperCase()} 方向进行三维等比例拉伸`,
|
||||
);
|
||||
if (options.silentInitial) {
|
||||
savedWorkspaceSnapshotRef.current = createWorkspaceSnapshot({
|
||||
modelPose: nextPose,
|
||||
@@ -2792,7 +2803,8 @@ export default function ReverseWorkspace({
|
||||
|
||||
const clampPoseValue = (key: ModelPoseKey, value: number) => {
|
||||
const limit = poseStepConfig[key];
|
||||
return clamp(value, limit.min, limit.max);
|
||||
const precision = getStepPrecision(limit.step);
|
||||
return Number(clamp(value, limit.min, limit.max).toFixed(precision));
|
||||
};
|
||||
|
||||
const updateModelPose = (partial: Partial<ModelPose>, options: { markCustom?: boolean; keepStatus?: boolean } = {}) => {
|
||||
|
||||
Reference in New Issue
Block a user