2026-05-24-18-59-49 修正薄壳构件实体化映射
This commit is contained in:
@@ -70,7 +70,7 @@ export const displayOptions: Array<{ id: DisplayLevel; label: string; limit: num
|
||||
{ id: 'standard', label: '标准', limit: 16000 },
|
||||
{ id: 'fine', label: '精细', limit: 36000 },
|
||||
{ id: 'ultra', label: '超精细', limit: 72000 },
|
||||
{ id: 'solid', label: '实体', limit: 200000 },
|
||||
{ id: 'solid', label: '实体', limit: 800000 },
|
||||
];
|
||||
export const dicomOpacityOptions: Array<{ id: DicomOpacityLevel; label: string; sliceOpacity: number; volumeOpacity: number; boxOpacity: number }> = [
|
||||
{ id: 'low', label: '低', sliceOpacity: 0.82, volumeOpacity: 0.12, boxOpacity: 0.32 },
|
||||
@@ -707,13 +707,14 @@ export function FusionThreeView({
|
||||
|
||||
const stlFiles = project.stlFiles ?? [];
|
||||
const visibleStlFiles = stlFiles.filter((fileName) => moduleStyles[fileName]?.visible !== false);
|
||||
const modelPreviewLimit = solidMode ? Math.max(detailLimit, 800000) : detailLimit;
|
||||
let modelBaseScale = 1;
|
||||
let loadedModels = 0;
|
||||
let failedModels = 0;
|
||||
const loadedBounds: Array<{ min: THREE.Vector3; max: THREE.Vector3 }> = [];
|
||||
|
||||
Promise.allSettled(stlFiles.map((fileName, index) => (
|
||||
getCachedModelPreview(project.id, fileName, detailLimit)
|
||||
getCachedModelPreview(project.id, fileName, modelPreviewLimit)
|
||||
.then((payload) => {
|
||||
if (disposed) return;
|
||||
const style = moduleStyles[fileName] ?? {
|
||||
@@ -732,11 +733,12 @@ export function FusionThreeView({
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.Float32BufferAttribute(payload.vertices, 3));
|
||||
geometry.computeVertexNormals();
|
||||
const materialOpacity = solidMode ? Math.max(style.opacity, 0.94) : style.opacity;
|
||||
const materialOpacity = solidMode ? 1 : style.opacity;
|
||||
const material = new THREE.MeshStandardMaterial({
|
||||
color: style.color,
|
||||
transparent: true,
|
||||
transparent: materialOpacity < 1,
|
||||
opacity: materialOpacity,
|
||||
depthWrite: materialOpacity >= 1,
|
||||
roughness: solidMode ? 0.56 : 0.48,
|
||||
metalness: 0.03,
|
||||
side: THREE.DoubleSide,
|
||||
@@ -1816,6 +1818,80 @@ function closeSmallMaskGaps(
|
||||
return toFill.size;
|
||||
}
|
||||
|
||||
function solidStrokeRadius(width: number, height: number) {
|
||||
return Math.max(2.2, Math.min(5.5, Math.max(width, height) * 0.006));
|
||||
}
|
||||
|
||||
function paintMaskPixel(
|
||||
maskData: ImageData,
|
||||
width: number,
|
||||
height: number,
|
||||
x: number,
|
||||
y: number,
|
||||
rgb: { r: number; g: number; b: number },
|
||||
alpha: number,
|
||||
) {
|
||||
if (x < 0 || x >= width || y < 0 || y >= height) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const offset = (y * width + x) * 4;
|
||||
if (maskData.data[offset + 3] > 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
maskData.data[offset] = rgb.r;
|
||||
maskData.data[offset + 1] = rgb.g;
|
||||
maskData.data[offset + 2] = rgb.b;
|
||||
maskData.data[offset + 3] = alpha;
|
||||
return 1;
|
||||
}
|
||||
|
||||
function fillSegmentCapsulesIntoMask(
|
||||
maskData: ImageData,
|
||||
width: number,
|
||||
height: number,
|
||||
segments: PlaneSegment[],
|
||||
rgb: { r: number; g: number; b: number },
|
||||
alpha: number,
|
||||
radius: number,
|
||||
) {
|
||||
let paintedPixels = 0;
|
||||
const radiusSquared = radius * radius;
|
||||
|
||||
segments.forEach(({ a, b }) => {
|
||||
if (!Number.isFinite(a.x) || !Number.isFinite(a.y) || !Number.isFinite(b.x) || !Number.isFinite(b.y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dx = b.x - a.x;
|
||||
const dy = b.y - a.y;
|
||||
const lengthSquared = dx * dx + dy * dy;
|
||||
const minX = clamp(Math.floor(Math.min(a.x, b.x) - radius), 0, width - 1);
|
||||
const maxX = clamp(Math.ceil(Math.max(a.x, b.x) + radius), 0, width - 1);
|
||||
const minY = clamp(Math.floor(Math.min(a.y, b.y) - radius), 0, height - 1);
|
||||
const maxY = clamp(Math.ceil(Math.max(a.y, b.y) + radius), 0, height - 1);
|
||||
|
||||
for (let y = minY; y <= maxY; y += 1) {
|
||||
for (let x = minX; x <= maxX; x += 1) {
|
||||
const px = x + 0.5;
|
||||
const py = y + 0.5;
|
||||
const t = lengthSquared <= 1e-6
|
||||
? 0
|
||||
: clamp(((px - a.x) * dx + (py - a.y) * dy) / lengthSquared, 0, 1);
|
||||
const closestX = a.x + dx * t;
|
||||
const closestY = a.y + dy * t;
|
||||
const distanceSquared = (px - closestX) ** 2 + (py - closestY) ** 2;
|
||||
if (distanceSquared <= radiusSquared) {
|
||||
paintedPixels += paintMaskPixel(maskData, width, height, x, y, rgb, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return paintedPixels;
|
||||
}
|
||||
|
||||
function drawFallbackClosedRegion(
|
||||
context: CanvasRenderingContext2D,
|
||||
width: number,
|
||||
@@ -1912,7 +1988,8 @@ function fillSegmentsAsSolidMask(
|
||||
}
|
||||
const maskData = maskContext.createImageData(width, height);
|
||||
let filledPixels = 0;
|
||||
const groups = groupPlaneSegmentsByConnectivity(segments);
|
||||
const radius = solidStrokeRadius(width, height);
|
||||
const groups = groupPlaneSegmentsByConnectivity(segments, radius * 1.15);
|
||||
const fallbackGroups: PlaneSegment[][] = [];
|
||||
|
||||
groups.forEach((group) => {
|
||||
@@ -1959,14 +2036,15 @@ function fillSegmentsAsSolidMask(
|
||||
}
|
||||
}
|
||||
});
|
||||
groupPixels += fillSegmentCapsulesIntoMask(maskData, width, height, group, rgb, alpha, radius);
|
||||
|
||||
filledPixels += groupPixels;
|
||||
if (groupPixels < Math.max(12, Math.round(group.length * 0.45)) && group.length >= 3) {
|
||||
if (groupPixels < Math.max(20, Math.round(group.length * 0.5)) && group.length >= 3) {
|
||||
fallbackGroups.push(group);
|
||||
}
|
||||
});
|
||||
|
||||
filledPixels += closeSmallMaskGaps(maskData, width, height, rgb, alpha);
|
||||
filledPixels += closeSmallMaskGaps(maskData, width, height, rgb, alpha, 3);
|
||||
filledPixels += fillInternalMaskHoles(maskData, width, height, rgb, alpha);
|
||||
maskContext.putImageData(maskData, 0, 0);
|
||||
context.drawImage(maskCanvas, 0, 0);
|
||||
@@ -2221,7 +2299,7 @@ export function VoxelizationMappingView({
|
||||
let disposed = false;
|
||||
let loaded = 0;
|
||||
const total = visibleStlFiles.length;
|
||||
const previewLimit = Math.max(detailLimit, 500000);
|
||||
const previewLimit = Math.max(detailLimit, 800000);
|
||||
const updateLoadProgress = (phase: string) => {
|
||||
if (!disposed) {
|
||||
setOverlayLoadState({ loading: true, loaded, total, phase });
|
||||
@@ -3396,7 +3474,7 @@ export default function ReverseWorkspace({
|
||||
const fusionStart = Math.min(displayStart, displayEnd);
|
||||
const fusionEnd = Math.max(displayStart, displayEnd);
|
||||
const previewLimit = selectedDisplay.limit;
|
||||
const mappingPreviewLimit = Math.max(previewLimit, 200000);
|
||||
const mappingPreviewLimit = Math.max(previewLimit, 800000);
|
||||
const total = 2 + stlFilesForLoad.length * 2;
|
||||
const startedAt = Date.now();
|
||||
let loaded = 0;
|
||||
|
||||
Reference in New Issue
Block a user