@@ -83,7 +83,7 @@ const createConcaveFilletShape = (radius: number): THREE.Shape => {
return shape ;
} ;
// New Helper: Create Perforated Plate (Vertical Wall )
// Вспомогательная функция: Создание перфорированной пластины (Вертикальная стенка )
const createPerforatedPlate = (
width : number ,
height : number ,
@@ -100,17 +100,17 @@ const createPerforatedPlate = (
shape . lineTo ( 0 , height ) ;
shape . lineTo ( 0 , 0 ) ;
// Hole Generation
// Генерация отверстий
if ( perf && perf . enabled && width > perf . size && height > perf . size ) {
const { shape : shapeType , size , gap } = perf ;
const step = size + gap ;
// Margins: ensure holes don't cut into the solid edge zones
// Отступы: предотвращаем попадание отверстий на сплошные края
const startX = Math . max ( gap , marginLeft + gap ) ;
const startY = gap ;
const endX = width - Math . max ( gap , marginRight + gap ) ;
const endY = height - gap ;
// Rows
// Строки
let row = 0 ;
for ( let y = startY + size / 2 ; y < endY ; y += ( shapeType === 'triangle' || shapeType === 'honeycomb' ? step * 0.866 : step ) ) {
const isStaggered = ( row % 2 !== 0 ) ;
@@ -120,18 +120,18 @@ const createPerforatedPlate = (
const hole = new THREE . Path ( ) ;
const r = size / 2 ;
// Boundary check
// Проверка границ
if ( x - r < marginLeft || x + r > width - marginRight || y - r < 0 || y + r > height ) continue ;
// Exclusion Zone Check
// Zone active if hole X is within [start, end] AND hole Y is below yMax (if specified ).
// If y > yMax, the exclusion doesn't apply (it's above the intersecting wall ).
// Note: y is measured from bottom (0) to top (height).
// yMax is the height of the intersecting partition .
// Проверка зон исключения
// Зона активна, если X отверстия входит в [start, end] И Y отверстия ниже yMax (если указан ).
// Если y > yMax, исключение не применяется (отверстие выше пересекающей стенки ).
// Примечание: y измеряется от низа (0) до верха (height).
// yMax - высота пересекающей перегородки .
const inExclusion = exclusions . some ( zone = > {
if ( x + r <= zone . start || x - r >= zone . end ) return false ; // X-axis check
if ( zone . yMax !== undefined && y - r > zone . yMax ) return false ; // Y-axis check (hole above wall )
if ( x + r <= zone . start || x - r >= zone . end ) return false ; // Проверка по оси X
if ( zone . yMax !== undefined && y - r > zone . yMax ) return false ; // Проверка по оси Y (отверстие выше стенки )
return true ;
} ) ;
@@ -140,7 +140,7 @@ const createPerforatedPlate = (
if ( shapeType === 'circle' ) {
hole . absarc ( x , y , r , 0 , Math . PI * 2 , true ) ;
} else if ( shapeType === 'honeycomb' ) {
// Hexagon
// Шестиугольник (Соты)
for ( let i = 0 ; i < 6 ; i ++ ) {
const ang = ( i * 60 + 30 ) * Math . PI / 180 ;
const px = x + r * Math . cos ( ang ) ;
@@ -150,7 +150,7 @@ const createPerforatedPlate = (
}
hole . closePath ( ) ;
} else if ( shapeType === 'triangle' ) {
// Triangle
// Треугольник
const ang1 = - 90 * Math . PI / 180 ;
const ang2 = 30 * Math . PI / 180 ;
const ang3 = 150 * Math . PI / 180 ;
@@ -166,37 +166,37 @@ const createPerforatedPlate = (
}
const geo = new THREE . ExtrudeGeometry ( shape , { depth : thickness , bevelEnabled : false } ) ;
// Extruded along Z. Wall is flat on XY.
// We want "thickness" to be Z depth .
// Выдавливание по Z. Стенка плоская в XY.
// Мы хотим, чтобы "thickness" была глубиной по Z .
return geo ;
} ;
// New Helper: Create Corner Profile (Extruded Vertical )
// Вспомогательная функция: Создание профиля угла (Выдавленный вертикально )
const createCornerProfile = ( radius : number , thickness : number , height : number ) : THREE . BufferGeometry = > {
if ( radius <= 0 ) return new THREE . BufferGeometry ( ) ;
const shape = new THREE . Shape ( ) ;
// Create a Ring Segment (Hollow Corner) as a single loop .
// Center at (0,0).
const innerRadius = Math . max ( 0.01 , radius - thickness ) ; // Ensure slightly > 0 to maintain shape integrity
// Создаем сегмент кольца (Полый угол) как единый контур .
// Центр в (0,0).
const innerRadius = Math . max ( 0.01 , radius - thickness ) ; // Гарантируем чуть > 0 для целостности формы
// 1. Start at Outer Start
// 1. Начало внешней дуги
shape . moveTo ( radius , 0 ) ;
// 2. Outer Arc (CCW ) -> To (0, radius)
// 2. Внешняя дуга (против часовой стрелки ) -> К (0, radius)
shape . absarc ( 0 , 0 , radius , 0 , Math . PI / 2 , false ) ;
// 3. Line to Inner End (0, innerRadius)
// 3. Линия к внутреннему концу (0, innerRadius)
shape . lineTo ( 0 , innerRadius ) ;
// 4. Inner Arc (CW ) -> To (innerRadius, 0)
// 4. Внутренняя дуга (по часовой стрелке ) -> К (innerRadius, 0)
shape . absarc ( 0 , 0 , innerRadius , Math . PI / 2 , 0 , true ) ;
// 5. Close loop
// 5. Замыкаем контур
shape . lineTo ( radius , 0 ) ;
// Extrude
// curveSegments 32 for smoothness
// Выдавливание
// curveSegments 32 для гладкости
const geo = new THREE . ExtrudeGeometry ( shape , { depth : height , bevelEnabled : false , curveSegments : 32 } ) ;
return geo ;
} ;
@@ -218,9 +218,9 @@ export const createBinGeometry = (
floorGeo . rotateX ( - Math . PI / 2 ) ; // Lay flat
geometries . push ( floorGeo ) ;
// WALLS
// СТЕНКИ
if ( ! perforation || ! perforation . enabled ) {
// --- ORIGINAL LOGIC (Optimized for Solid Walls ) ---
// --- ЛОГИКА ДЛЯ СПЛОШНЫХ С Т Е Н (Без перфорации ) ---
const outerShape = createRoundedRectShape ( width , depth , radius ) ;
const innerRadius = Math . max ( 0.1 , radius - thickness ) ;
const innerWidth = width - ( 2 * thickness ) ;
@@ -237,78 +237,78 @@ export const createBinGeometry = (
wallGeo . translate ( 0 , thickness , 0 ) ;
geometries . push ( wallGeo ) ;
} else {
// --- PERFORATED LOGIC (Split Walls ) ---
// --- ЛОГИКА ДЛЯ ПЕРФОРИРОВАННЫХ С Т Е Н (Раздельные части ) ---
const wallHeight = height - thickness ;
// Clamp radius to at least thickness for valid corners in this mode
// Ограничиваем радиус минимум толщиной стенки для корректных углов
const effRadius = Math . max ( radius , thickness ) ;
const straightW = width - 2 * effRadius ;
const straightD = depth - 2 * effRadius ;
// 1. Corners (4 pcs )
// 1. Углы (4 шт )
if ( effRadius > 0 ) {
const cornerGeoBase = createCornerProfile ( effRadius , thickness , wallHeight ) ;
// 1. Stand Up: Extrusion Z -> Y. Shape moves to X(+)/Z(+).
// 1. Поднимаем: Выдавливание Z -> Y. Форма перемещается в X(+)/Z(+).
cornerGeoBase . rotateX ( - Math . PI / 2 ) ;
// Base Corner Shape (after rotateX) is Q4 (+X, -Z).
// Rotation Logic: -90 degrees per Quadrant (Standard Three.js Y-Rot ).
// Базовая форма угла (после rotateX) это Q4 (+X, -Z).
// Логика вращения: -90 градусов на квадрант (Стандартный Y-Rot в Three.js).
// Q4 (0) -> Q1 (-90) -> Q2 (-180/180) -> Q3 (-270/+90).
const positions = [
// Back Right (+X, +Z). Q1.
// Rot -90 (-PI/2).
// Задний Правый (+X, +Z). Q1.
// Поворот -90 (-PI/2).
{ x : width / 2 - effRadius , z : depth / 2 - effRadius , rot : - Math . PI / 2 } ,
// Back Left (-X, +Z). Q2.
// Rot 180 (PI).
// Задний Левый (-X, +Z). Q2.
// Поворот 180 (PI).
{ x : - ( width / 2 - effRadius ) , z : depth / 2 - effRadius , rot : Math.PI } ,
// Front Left (-X, -Z). Q3.
// Rot 90 (PI/2). (Equivalent to -270).
// Передний Левый (-X, -Z). Q3.
// Поворот 90 (PI/2). (Эквивалентно -270).
{ x : - ( width / 2 - effRadius ) , z : - ( depth / 2 - effRadius ) , rot : Math.PI / 2 } ,
// Front Right (+X, -Z). Q4.
// Rot 0.
// Передний Правый (+X, -Z). Q4.
// Поворот 0.
{ x : width / 2 - effRadius , z : - ( depth / 2 - effRadius ) , rot : 0 }
] ;
positions . forEach ( pos = > {
const corner = cornerGeoBase . clone ( ) ;
corner . rotateY ( pos . rot ) ;
corner . translate ( pos . x , 0 , pos . z ) ; // Start at Y=0
corner . translate ( 0 , thickness , 0 ) ; // Move on top of floor
corner . translate ( pos . x , 0 , pos . z ) ; // Старт на Y=0
corner . translate ( 0 , thickness , 0 ) ; // Сдвиг на толщину дна
geometries . push ( corner ) ;
} ) ;
}
// 2. Straight Walls (4 pcs ) - Centered on edges
// 2. Прямые стены (4 шт ) - Центрированы по краям
// REWRITE EXCLUSION COLLECTION LOGIC TO BE WALL-SPECIFIC
// ЛОГИКА СБОРА ИСКЛЮЧЕНИЙ ДЛЯ С Т Е Н
const exclusionsBack : { start : number , end : number , yMax : number } [ ] = [ ] ;
const exclusionsFront : { start : number , end : number , yMax : number } [ ] = [ ] ;
const exclusionsLeft : { start : number , end : number , yMax : number } [ ] = [ ] ;
const exclusionsRight : { start : number , end : number , yMax : number } [ ] = [ ] ;
// Inner Dimensions
// Внутренние размеры
const effectiveInnerW = width - 2 * thickness ;
const effectiveInnerD = depth - 2 * thickness ;
partitions . forEach ( p = > {
const hEff = Math . max ( 0.1 , p . height - thickness ) ; // Exclude only up to partition height
// Ensure solid strip around partition by adding padding to exclusion zone
const hEff = Math . max ( 0.1 , p . height - thickness ) ; // Исключаем только до высоты перегородки
// Гарантируем сплошную полосу вокруг перегородки добавлением отступа к зоне исключения
const padding = ( perforation ? . gap ? ? 2 ) + 1 ;
if ( p . axis === 'x' ) {
// Runs Depth-wise (Y axis in Layout ).
// Intersects Front and Back walls .
// Идет по глубине (Ось Y в макете ).
// Пересекает Переднюю и Заднюю стенки .
const partGlobalX = ( - effectiveInnerW / 2 ) + ( effectiveInnerW * p . offset ) ;
const sW = width - 2 * effRadius ;
const localXOnWall = partGlobalX + sW / 2 ;
// Check intersection with active straight wall area + padding
// Проверка пересечения с активной зоной стены + отступ
if ( localXOnWall + thickness / 2 + padding > 0 && localXOnWall - thickness / 2 - padding < sW ) {
const zone = {
start : localXOnWall - thickness / 2 - padding ,
@@ -316,13 +316,13 @@ export const createBinGeometry = (
yMax : hEff
} ;
if ( ( p . max ? ? 1 ) > 0.99 ) exclusionsFront . push ( zone ) ; // Near
if ( ( p . min ? ? 0 ) < 0.01 ) exclusionsBack . push ( zone ) ; // Far
if ( ( p . max ? ? 1 ) > 0.99 ) exclusionsFront . push ( zone ) ; // Ближняя
if ( ( p . min ? ? 0 ) < 0.01 ) exclusionsBack . push ( zone ) ; // Дальняя
}
} else { // p.axis === 'y'
// Runs Width-wise (X axis in Layout ).
// Intersects Left and Right walls .
// Идет по ширине (Ось X в макете ).
// Пересекает Левую и Правую стенки .
const partGlobalZ = ( - effectiveInnerD / 2 ) + ( effectiveInnerD * p . offset ) ;
const sD = depth - 2 * effRadius ;
@@ -345,21 +345,21 @@ export const createBinGeometry = (
}
} ) ;
// Front/Back
// Передняя/Задняя
if ( straightW > 0.1 ) {
// Wall 1 (at +Z). This is "Front" (Near). Contacts p.max.
// Uses exclusionsFront.
// Стенка 1 (+Z). Это "Передняя" (Ближняя). Контактирует с p.max.
// Использует exclusionsFront.
const wGeoFront = createPerforatedPlate ( straightW , wallHeight , thickness , perforation , 0 , 0 , exclusionsFront ) ;
wGeoFront . translate ( - straightW / 2 , 0 , 0 ) ;
const w1 = wGeoFront . clone ( ) ;
w1 . translate ( 0 , thickness , depth / 2 - thickness ) ;
geometries . push ( w1 ) ;
// Wall 2 (at -Z). This is "Back" (Far). Contacts p.min.
// Uses exclusionsBack.
// Needs checking mapping (Rot 180).
// p.offset increases X. Wall rotated 180 means X is inverted .
// So we map exclusionsBack.
// Стенка 2 (-Z). Это "Задняя" (Дальняя). Контактирует с p.min.
// Использует exclusionsBack.
// Нужна проверка маппинга (Поворот 180).
// p.offset увеличивает X. Стенка повернута на 180, значит X инвертирован .
// Маппим exclusionsBack.
const exclusionsBackMapped = exclusionsBack . map ( e = > ( {
start : straightW - e . end ,
end : straightW - e . start ,
@@ -374,27 +374,23 @@ export const createBinGeometry = (
geometries . push ( w2 ) ;
}
// Left/Right
// Левая/Правая
if ( straightD > 0.1 ) {
// Wall 3 (Right? +X).
// Plate is 0..straightD in X, 0..wallHeight in Y. Thickness along Z.
// We want it to be at X = width/2.
// It needs to be rotated -PI/2 around Y to align its X-axis with World Z-axis .
// After rotateY(-PI/2): Plate X (0..straightD) becomes World Z (0..straightD).
// Plate Z (thickness) becomes World -X (towards Left).
// So if we place it at X=width/2, it extrudes to width/2 - thickness. Correct for Right Wall.
// Стенка 3 (Правая +X).
// Пластина 0..straightD по X, 0..wallHeight по Y. Толщина по Z.
// Хотим разместить на X = width/2.
// Нужно повернуть -PI/2 вокруг Y чтобы выровнять ось X пластины с мировой осью Z .
const wGeoRight = createPerforatedPlate ( straightD , wallHeight , thickness , perforation , 0 , 0 , exclusionsRight ) ;
wGeoRight . translate ( - straightD / 2 , 0 , 0 ) ; // Center X of plate
wGeoRight . translate ( - straightD / 2 , 0 , 0 ) ; // Центр X пластины
const w3 = wGeoRight . clone ( ) ;
w3 . rotateY ( - Math . PI / 2 ) ; // Rotate to align with Z-axis
w3 . translate ( width / 2 , thickness , 0 ) ; // Position at X=width/2
w3 . rotateY ( - Math . PI / 2 ) ; // Поворот
w3 . translate ( width / 2 , thickness , 0 ) ; // Позиция на X=width/2
geometries . push ( w3 ) ;
// Wall 4 (Left? -X).
// This wall is also rotated PI/2.
// Plate Z (thickness) becomes World X (towards Right ).
// We want it at X=-width/2.
// It will extrude to -width/2 + thickness. Correct for Left Wall.
// Стенка 4 (Левая -X).
// Также повернута на PI/2.
// Толщина по Z станем Мировой X (направо ).
// Хотим на X=-width/2.
const exclusionsLeftMapped = exclusionsLeft . map ( e = > ( {
start : straightD - e . end ,
end : straightD - e . start ,
@@ -402,10 +398,10 @@ export const createBinGeometry = (
} ) ) ;
const wGeoLeft = createPerforatedPlate ( straightD , wallHeight , thickness , perforation , 0 , 0 , exclusionsLeftMapped ) ;
wGeoLeft . translate ( - straightD / 2 , 0 , 0 ) ; // Center X of plate
wGeoLeft . translate ( - straightD / 2 , 0 , 0 ) ; // Центр X пластины
const w4 = wGeoLeft . clone ( ) ;
w4 . rotateY ( Math . PI / 2 ) ;
w4 . translate ( - width / 2 , thickness , 0 ) ; // Position at X=-width/2
w4 . translate ( - width / 2 , thickness , 0 ) ; // Позиция на X=-width/2
geometries . push ( w4 ) ;
}
}
@@ -413,7 +409,7 @@ export const createBinGeometry = (
// ВНУТРЕННИЕ ПЕРЕГОРОДКИ
const innerWidth = width - ( 2 * thickness ) ;
const innerDepth = depth - ( 2 * thickness ) ; // Approximate usable space logic
const innerDepth = depth - ( 2 * thickness ) ; // Приблизительное полезное пространство
partitions . forEach ( p = > {
const pMin = p . min ? ? 0 ;
@@ -424,97 +420,84 @@ export const createBinGeometry = (
const lengthRatio = pMax - pMin ;
const midRatio = pMin + ( lengthRatio / 2 ) ;
// FIX: Subtract thickness because partitions sit ON TOP of the floor
// ИСПРАВЛЕНИЕ: Вычитаем толщину, так как перегородки стоят Н А дне
const effectiveHeight = Math . max ( 0.1 , p . height - thickness ) ;
// --- PERFORATED LOGIC FOR PARTITIONS ---
// If enabled, use Plate. Else use Extrude Solid .
// --- ЛОГИКА ДЛЯ ПЕРФОРИРОВАННЫХ ПЕРЕГОРОДОК ---
// Если включено, используем Пластину. Иначе - сплошное Выдавливание .
const usePerf = perforation && perforation . enabled ;
let pX = 0 , pY = 0 ; // Declare here for visibility in Fillets
let pX = 0 , pY = 0 ; // Объявляем здесь для видимости в Скруглениях
if ( usePerf ) {
// Calculate exact geometry
// Вычисляем точную геометрию
let pLen = 0 ;
if ( p . axis === 'x' ) {
// Axis X -> Divider runs along Y (Depth )
// Ось X -> Разделитель идет вдоль Y (Глубина )
pLen = lengthRatio * innerDepth ;
pX = ( - innerWidth / 2 ) + ( innerWidth * p . offset ) ;
pY = ( - innerDepth / 2 ) + ( innerDepth * midRatio ) ; // Center of partition
pY = ( - innerDepth / 2 ) + ( innerDepth * midRatio ) ; // Центр перегородки
// Calculate Exclusions for Internal Partition
// Вычисляем исключения для внутренней перегородки
const partExclusions : { start : number , end : number , yMax? : number } [ ] = [ ] ;
// This partition is 'p' (Axis X, runs along Depth ).
// Intersected by partitions 'n' (Axis Y, runs along Width ).
// p global Y range: pY - pLen/2 to pY + pLen/2.
// n global Y pos: (-innerDepth/2) + (innerDepth * n.offset).
// Эта перегородка 'p' (Ось X, идет по Глубине ).
// Пересекается перегородками 'n' (Ось Y, идут по Ширине ).
partitions . forEach ( n = > {
if ( n . axis !== 'y' ) return ; // Only orthogonal partitions intersect
// Check if n intersects p
// Intersection Y location :
if ( n . axis !== 'y' ) return ; // Пересекаются только ортогональные перегородки
// Проверяем, пересекает ли n перегородку p
// Y-координата пересечения :
const nY = ( - innerDepth / 2 ) + ( innerDepth * n . offset ) ;
// Does nY (which is global Y of the intersecting partition) match p's position?
// p is Axis X, runs along DEPTH (Global Y).
// p is centered at pX (Width) and covers pLen in Depth (Y).
// Wait. p (Axis X) runs along Y?
// p.axis='x' -> "Runs along Y (Depth)". Correct.
// So p covers Y range [pY - pLen/2, pY + pLen/2].
// n is Axis Y, runs along X (Width).
// n is centered at nY (Depth).
// So intersection happens if nY is within p's Y range.
const pStartGlobal = pY - pLen / 2 ;
const pEndGlobal = pY + pLen / 2 ;
// And check if n covers p's X location .
// n runs along X from nMin to nMax.
// И проверяем, покрывает ли n позицию X перегородки p .
// n идет по X от nMin до nMax.
const nXStart = ( - innerWidth / 2 ) + ( innerWidth * ( n . min ? ? 0 ) ) ;
const nXEnd = ( - innerWidth / 2 ) + ( innerWidth * ( n . max ? ? 1 ) ) ;
const pXLoc = pX ;
if ( nY >= pStartGlobal && nY <= pEndGlobal && pXLoc >= nXStart && pXLoc <= nXEnd ) {
// Intersection confirmed .
// Calculate local coord on p's plate .
// p runs along Y. Plate local X maps to Global Y.
// local = global - pY + pLen/2.
// Пересечение подтверждено .
// Вычисляем локальные координаты на пластине p .
// p идет по Y. Локальный X пластины мапится на глобальный Y.
const nHeight = Math . max ( 0.1 , n . height - thickness ) ;
const localX = nY - pY + pLen / 2 ;
partExclusions . push ( { start : localX - thickness / 2 , end : localX + thickness / 2 , yMax : nHeight } ) ;
}
} ) ;
// Add margins (solid ends )
// Добавляем отступы (сплошные концы )
const margin = thickness ;
const plate = createPerforatedPlate ( pLen , effectiveHeight , thickness , perforation ! , margin , margin , partExclusions ) ;
plate . translate ( - pLen / 2 , 0 , 0 ) ; // Center X
plate . translate ( - pLen / 2 , 0 , 0 ) ; // Центр X
// Rotate to align with Depth (along Z)
// Plate X -> Z
// Поворачиваем для выравнивания по Глубине (вдоль Z)
// Пластина X -> Z
plate . rotateY ( - Math . PI / 2 ) ;
// Position
// Plate is now vertical Z-aligned. Thickness along X.
// Позиционирование
// Пластина теперь вертикально по Z. Толщина по X.
plate . translate ( pX + thickness / 2 , thickness , pY ) ;
geometries . push ( plate ) ;
} else {
// Axis Y -> Divider runs along X (Width )
// Ось Y -> Разделитель идет вдоль X (Ширина )
pLen = lengthRatio * innerWidth ;
pX = ( - innerWidth / 2 ) + ( innerWidth * midRatio ) ;
pY = ( - innerDepth / 2 ) + ( innerDepth * p . offset ) ;
// Calculate Exclusions
// Вычисление исключений
const partExclusions : { start : number , end : number , yMax? : number } [ ] = [ ] ;
// This partition is 'p' (Axis Y, runs along Width ).
// Intersected by partitions 'n' (Axis X, runs along Depth ).
// Эта перегородка 'p' (Ось Y, идет по Ширине ).
// Пересекается перегородками 'n' (Ось X, идут по Глубине ).
partitions . forEach ( n = > {
if ( n . axis !== 'x' ) return ;
const nX = ( - innerWidth / 2 ) + ( innerWidth * n . offset ) ;
const pStartGlobal = pX - pLen / 2 ; // X range of p
const pStartGlobal = pX - pLen / 2 ; // Диапазон X для p
const pEndGlobal = pX + pLen / 2 ;
const nYStart = ( - innerDepth / 2 ) + ( innerDepth * ( n . min ? ? 0 ) ) ;
@@ -522,9 +505,8 @@ export const createBinGeometry = (
const pYLoc = pY ;
if ( nX >= pStartGlobal && nX <= pEndGlobal && pYLoc >= nYStart && pYLoc <= nYEnd ) {
// Intersection confirmed
// local = global - pX + pLen/2
const nHeight = Math . max ( 0.1 , n . height - thickness ) ; // INTERSECTING PARTITION HEIGHT
// Пересечение подтверждено
const nHeight = Math . max ( 0.1 , n . height - thickness ) ; // ВЫСОТА ПЕРЕСЕКАЮЩЕЙ ПЕРЕГОРОДКИ
const localX = nX - pX + pLen / 2 ;
partExclusions . push ( { start : localX - thickness / 2 , end : localX + thickness / 2 , yMax : nHeight } ) ;
}
@@ -532,20 +514,19 @@ export const createBinGeometry = (
const margin = thickness ;
const plate = createPerforatedPlate ( pLen , effectiveHeight , thickness , perforation ! , margin , margin , partExclusions ) ;
plate . translate ( - pLen / 2 , 0 , 0 ) ; // Center X
plate . translate ( - pLen / 2 , 0 , 0 ) ; // Центр X
// Already aligned with X. Thickness along Z.
// Z range [0, th]. We want [-th/2, th/2] relative to pY.
// Translate Z by -th/2.
// Уже выровнено по X. Толщина по Z.
// Диапазон Z [0, th]. Мы хотим [-th/2, th/2] относительно pY.
plate . translate ( 0 , 0 , - thickness / 2 ) ;
// Move to position
// Перемещение на позицию
plate . translate ( pX , thickness , pY ) ;
geometries . push ( plate ) ;
}
} else {
// --- SOLID LOGIC ---
// --- ЛОГИКА ДЛЯ СПЛОШНЫХ ПЕРЕГОРОДОК ---
let pWidth = 0 , pDepth = 0 ;
if ( p . axis === 'x' ) {
pWidth = thickness ;
@@ -566,12 +547,12 @@ export const createBinGeometry = (
geometries . push ( partGeo ) ;
}
// Fillets Logic for partitions (Keep solid for strength/aesthetics )
// Логика скруглений для перегородок (Оставляем сплошными для прочности/эстетики )
if ( p . rounded && radius > 1 ) {
const filletR = Math . min ( radius , 5 ) ;
const filletShape = createConcaveFilletShape ( filletR ) ;
// Helper to get neighbor height
// Помощник для получения высоты соседа
const getNeighborHeight = ( pos : number ) = > {
if ( pos < 0.001 || pos > 0.999 ) return height ;
const neighbor = partitions . find ( n = > {