test #2
@@ -11,6 +11,7 @@ export const calculateParts = (
|
|||||||
): GeneratedPart[] => {
|
): GeneratedPart[] => {
|
||||||
const parts: GeneratedPart[] = [];
|
const parts: GeneratedPart[] = [];
|
||||||
|
|
||||||
|
// Сортируем линии разреза
|
||||||
const xPoints = [0, ...[...splits.x].sort((a, b) => a - b), 1];
|
const xPoints = [0, ...[...splits.x].sort((a, b) => a - b), 1];
|
||||||
const yPoints = [0, ...[...splits.y].sort((a, b) => a - b), 1];
|
const yPoints = [0, ...[...splits.y].sort((a, b) => a - b), 1];
|
||||||
|
|
||||||
@@ -24,12 +25,13 @@ export const calculateParts = (
|
|||||||
const segmentW = (xPoints[i + 1] - xPoints[i]) * config.drawer.width;
|
const segmentW = (xPoints[i + 1] - xPoints[i]) * config.drawer.width;
|
||||||
const segmentD = (yPoints[j + 1] - yPoints[j]) * config.drawer.depth;
|
const segmentD = (yPoints[j + 1] - yPoints[j]) * config.drawer.depth;
|
||||||
|
|
||||||
// Apply Printer Tolerance
|
// Применяем зазор (Tolerance)
|
||||||
const realWidth = segmentW - config.printerTolerance;
|
const realWidth = segmentW - config.printerTolerance;
|
||||||
const realDepth = segmentD - config.printerTolerance;
|
const realDepth = segmentD - config.printerTolerance;
|
||||||
const realX = segmentX + (config.printerTolerance / 2);
|
const realX = segmentX + (config.printerTolerance / 2);
|
||||||
const realY = segmentY + (config.printerTolerance / 2);
|
const realY = segmentY + (config.printerTolerance / 2);
|
||||||
|
|
||||||
|
// Игнорируем слишком мелкие детали
|
||||||
if (realWidth < 5 || realDepth < 5) {
|
if (realWidth < 5 || realDepth < 5) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -52,25 +54,25 @@ export const calculateParts = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to create a rounded rectangle Shape
|
* Создает 2D форму прямоугольника со скругленными краями
|
||||||
*/
|
*/
|
||||||
const createRoundedRectShape = (width: number, height: number, radius: number): THREE.Shape => {
|
const createRoundedRectShape = (width: number, height: number, radius: number): THREE.Shape => {
|
||||||
const shape = new THREE.Shape();
|
const shape = new THREE.Shape();
|
||||||
const x = -width / 2;
|
const x = -width / 2;
|
||||||
const y = -height / 2;
|
const y = -height / 2;
|
||||||
|
|
||||||
// Clamp radius to not exceed half of width or height
|
// Ограничиваем радиус, чтобы он не сломал геометрию (не больше половины стороны)
|
||||||
const r = Math.min(radius, width / 2, height / 2);
|
const r = Math.min(radius, width / 2, height / 2);
|
||||||
|
|
||||||
if (r <= 0) {
|
if (r <= 0.1) {
|
||||||
// Regular rectangle
|
// Обычный прямоугольник (если радиус 0)
|
||||||
shape.moveTo(x, y);
|
shape.moveTo(x, y);
|
||||||
shape.lineTo(x + width, y);
|
shape.lineTo(x + width, y);
|
||||||
shape.lineTo(x + width, y + height);
|
shape.lineTo(x + width, y + height);
|
||||||
shape.lineTo(x, y + height);
|
shape.lineTo(x, y + height);
|
||||||
shape.lineTo(x, y);
|
shape.lineTo(x, y);
|
||||||
} else {
|
} else {
|
||||||
// Rounded rectangle
|
// Прямоугольник со скруглениями
|
||||||
shape.moveTo(x, y + r);
|
shape.moveTo(x, y + r);
|
||||||
shape.lineTo(x, y + height - r);
|
shape.lineTo(x, y + height - r);
|
||||||
shape.quadraticCurveTo(x, y + height, x + r, y + height);
|
shape.quadraticCurveTo(x, y + height, x + r, y + height);
|
||||||
@@ -86,42 +88,34 @@ const createRoundedRectShape = (width: number, height: number, radius: number):
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a Three.js Geometry for a hollow bin with fillets (rounded corners).
|
* Генерирует 3D геометрию ящика
|
||||||
*/
|
*/
|
||||||
export const createBinGeometry = (
|
export const createBinGeometry = (
|
||||||
width: number,
|
width: number,
|
||||||
depth: number,
|
depth: number,
|
||||||
height: number,
|
height: number,
|
||||||
thickness: number,
|
thickness: number,
|
||||||
radius: number = 0 // Default radius
|
radius: number = 0
|
||||||
): THREE.BufferGeometry => {
|
): THREE.BufferGeometry => {
|
||||||
|
|
||||||
// 1. Создаем форму ДНА (Floor)
|
// 1. ГЕОМЕТРИЯ ДНА (Сплошная)
|
||||||
// Это сплошной прямоугольник со скруглениями
|
|
||||||
const floorShape = createRoundedRectShape(width, depth, radius);
|
const floorShape = createRoundedRectShape(width, depth, radius);
|
||||||
|
|
||||||
const floorGeo = new THREE.ExtrudeGeometry(floorShape, {
|
const floorGeo = new THREE.ExtrudeGeometry(floorShape, {
|
||||||
depth: thickness, // Толщина дна
|
depth: thickness, // Выдавливаем на толщину дна
|
||||||
bevelEnabled: false,
|
bevelEnabled: false,
|
||||||
curveSegments: 12 // Гладкость скругления
|
curveSegments: 16 // Количество сегментов на скруглениях
|
||||||
});
|
});
|
||||||
|
|
||||||
// ExtrudeGeometry создает объект "лежа" на XY, нам нужно повернуть его, чтобы он стал дном (XZ)
|
// Extrude выдавливает по оси Z. Нам нужно повернуть, чтобы "глубина" стала "высотой" (Y).
|
||||||
floorGeo.rotateX(Math.PI / 2);
|
// Поворот на -90 градусов вокруг X кладет Z на Y.
|
||||||
// Сдвигаем на высоту thickness, так как extrude идет в +Z (после поворота это +Y, но перевернуто... проще подобрать)
|
floorGeo.rotateX(-Math.PI / 2);
|
||||||
// По умолчанию extrude создает от 0 до Z. После поворота X(90deg):
|
// Теперь дно занимает пространство от Y=0 до Y=thickness.
|
||||||
// Z становится -Y. То есть дно уходит вниз от 0.
|
|
||||||
// Нам нужно, чтобы дно было от 0 до thickness по Y.
|
|
||||||
// Возвращаем поворот в -90 (стандарт для топологии)
|
|
||||||
floorGeo.rotateX(-Math.PI);
|
|
||||||
floorGeo.translate(0, thickness, 0); // Поднимаем, чтобы верх дна был на уровне thickness
|
|
||||||
|
|
||||||
|
// 2. ГЕОМЕТРИЯ СТЕНОК (С дыркой)
|
||||||
// 2. Создаем форму СТЕНОК (Walls)
|
|
||||||
// Это внешняя форма МИНУС внутренняя форма (дырка)
|
|
||||||
const outerShape = createRoundedRectShape(width, depth, radius);
|
const outerShape = createRoundedRectShape(width, depth, radius);
|
||||||
|
|
||||||
// Внутренний радиус должен быть меньше внешнего на толщину стенки, но не меньше 0
|
// Вырезаем внутреннюю часть
|
||||||
const innerRadius = Math.max(0, radius - thickness);
|
const innerRadius = Math.max(0, radius - thickness);
|
||||||
const innerWidth = width - (2 * thickness);
|
const innerWidth = width - (2 * thickness);
|
||||||
const innerDepth = depth - (2 * thickness);
|
const innerDepth = depth - (2 * thickness);
|
||||||
@@ -131,29 +125,33 @@ export const createBinGeometry = (
|
|||||||
outerShape.holes.push(innerHole);
|
outerShape.holes.push(innerHole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Высота стенок = общая высота минус толщина дна
|
||||||
const wallHeight = height - thickness;
|
const wallHeight = height - thickness;
|
||||||
|
|
||||||
const wallGeo = new THREE.ExtrudeGeometry(outerShape, {
|
const wallGeo = new THREE.ExtrudeGeometry(outerShape, {
|
||||||
depth: wallHeight,
|
depth: wallHeight,
|
||||||
bevelEnabled: false,
|
bevelEnabled: false,
|
||||||
curveSegments: 12
|
curveSegments: 16
|
||||||
});
|
});
|
||||||
|
|
||||||
// Поворачиваем стенки так же, как дно
|
|
||||||
wallGeo.rotateX(Math.PI / 2);
|
|
||||||
wallGeo.rotateX(-Math.PI);
|
|
||||||
// Стенки начинаются ОТ дна (y = thickness)
|
|
||||||
wallGeo.translate(0, thickness + wallHeight, 0);
|
|
||||||
|
|
||||||
// Объединяем геометрии
|
// Поворачиваем стенки так же, как дно
|
||||||
|
wallGeo.rotateX(-Math.PI / 2);
|
||||||
|
|
||||||
|
// Сейчас стенки тоже начинаются с Y=0.
|
||||||
|
// Нам нужно поднять их НАД дном.
|
||||||
|
wallGeo.translate(0, thickness, 0);
|
||||||
|
|
||||||
|
// Теперь стенки занимают пространство от Y=thickness до Y=height.
|
||||||
|
|
||||||
|
// 3. ОБЪЕДИНЕНИЕ
|
||||||
|
// Сливаем две геометрии в одну. Слайсеры поймут это как единый объект,
|
||||||
|
// так как поверхности идеально соприкасаются.
|
||||||
const merged = mergeBufferGeometries([floorGeo, wallGeo]);
|
const merged = mergeBufferGeometries([floorGeo, wallGeo]);
|
||||||
|
|
||||||
// Центрируем геометрию, чтобы Pivot был в центре дна (как раньше в BinMesh)
|
// Центрирование не нужно, так как createRoundedRectShape строит форму вокруг (0,0) по X и Z.
|
||||||
// Ранее мы использовали BoxGeometry который центрирован.
|
// А по Y мы выстроили от 0 вверх.
|
||||||
// Extrude создает форму вокруг (0,0), так что по X/Z она уже центрирована.
|
// Pivot point (опорная точка) осталась внизу в центре (0,0,0), что идеально для позиционирования.
|
||||||
// По Y она сейчас от 0 до height.
|
|
||||||
// Вернем как было: Pivot внизу.
|
|
||||||
|
|
||||||
return merged || new THREE.BoxGeometry(1, 1, 1);
|
return merged || new THREE.BoxGeometry(1, 1, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user