@@ -188,110 +188,117 @@ function ProjectEditorPage() {
188188 forcePosition
189189 ) ;
190190
191- // 스태킹 가능한 대상 찾기
192- const potentialTargets = allBlocks
193- . filter ( ( block ) => block . id !== newBlock . id )
194- . filter ( ( block ) => canStack ( newBlock . type , block . type ) )
195- . filter ( ( block ) => validateStacking ( newBlock , block ) ) ;
196-
197- if ( potentialTargets . length > 0 ) {
198- // Compute 인스턴스의 경우 물리적으로 가까운 대상과만 스태킹 관계 생성
199- const isComputeInstance = newBlock . type . includes ( 'ec2' ) || newBlock . type . includes ( 'compute-engine' ) || newBlock . type . includes ( 'virtual-machine' ) ;
200- if ( isComputeInstance ) {
201- console . log (
202- "🔗 [NewStacking] EC2 다중 스태킹 처리:" ,
203- potentialTargets . map ( ( t ) => t . type )
204- ) ;
191+ const isComputeInstance = newBlock . type . includes ( 'ec2' ) || newBlock . type . includes ( 'compute-engine' ) || newBlock . type . includes ( 'virtual-machine' ) ;
205192
206- // 거리 기반으로 필터링하여 정말 가까운 대상만 선택
207- const closeTargets = potentialTargets . filter ( ( target ) => {
208- const distance = Math . sqrt (
209- Math . pow ( newBlock . position . x - target . position . x , 2 ) +
210- Math . pow ( newBlock . position . z - target . position . z , 2 )
211- ) ;
193+ if ( isComputeInstance ) {
194+ // ===== EC2/Compute 전용 로직: 겹침 면적 기반 =====
195+ console . log ( "🔗 [NewStacking] Compute 인스턴스 스태킹 처리" ) ;
196+ console . log ( "📍 [NewStacking] EC2 현재 위치:" , {
197+ x : newBlock . position . x . toFixed ( 2 ) ,
198+ y : newBlock . position . y . toFixed ( 2 ) ,
199+ z : newBlock . position . z . toFixed ( 2 )
200+ } ) ;
212201
213- // 부트볼륨 연결(Compute-Volume/Disk)은 매우 가까워야 함 (거리 1.5 이하)
214- const isVolumeDisk = target . type . includes ( 'volume' ) || target . type . includes ( 'ebs' ) || target . type . includes ( 'disk' ) ;
215- if ( isVolumeDisk ) {
216- const isVeryClose = distance <= 1.5 ;
217- console . log ( "🔍 [NewStacking] 부트볼륨 거리 검사:" , {
218- target : target . type ,
219- distance : distance . toFixed ( 2 ) ,
220- isVeryClose,
221- } ) ;
222- return isVeryClose ;
223- }
202+ // 1단계: 타입별로 분류
203+ const volumes : DroppedBlock [ ] = [ ] ;
204+ const subnets : DroppedBlock [ ] = [ ] ;
224205
225- // Subnet 연결은 더 관대하게 (거리 5.0 이하)
226- if ( target . type . includes ( 'subnet' ) ) {
227- const isClose = distance <= 5.0 ;
228- console . log ( "🔍 [NewStacking] Subnet 거리 검사:" , {
229- distance : distance . toFixed ( 2 ) ,
230- isClose,
231- } ) ;
232- return isClose ;
233- }
206+ allBlocks . forEach ( block => {
207+ if ( block . id === newBlock . id ) return ;
208+ if ( ! canStack ( newBlock . type , block . type ) ) return ;
234209
235- return false ;
236- } ) ;
210+ const isVolumeDisk = block . type . includes ( 'volume' ) || block . type . includes ( 'ebs' ) || block . type . includes ( 'disk' ) ;
211+ const isSubnet = block . type . includes ( 'subnet' ) ;
237212
238- console . log (
239- "🔗 [NewStacking] 거리 필터링 후 대상:" ,
240- closeTargets . map ( ( t ) => t . type )
241- ) ;
213+ if ( isVolumeDisk ) volumes . push ( block ) ;
214+ if ( isSubnet ) subnets . push ( block ) ;
215+ } ) ;
216+
217+ console . log ( "[NewStacking] 타입별 분류:" , {
218+ volumeCount : volumes . length ,
219+ subnetCount : subnets . length ,
220+ } ) ;
221+
222+ // 2단계: Volume/EBS 검증 (validateStacking 사용)
223+ const validVolumes = volumes . filter ( vol => {
224+ const isValid = validateStacking ( newBlock , vol ) ;
225+ console . log ( `[NewStacking] Volume 검증: ${ vol . type . substring ( 0 , 10 ) } - ${ isValid ? '✅' : '❌' } ` ) ;
226+ return isValid ;
227+ } ) ;
242228
243- // 가까운 대상과만 스태킹 관계 생성
244- closeTargets . forEach ( ( target ) => {
245- createStackingRelation ( newBlock . id , target . id , allBlocks ) ;
246- console . log ( "🔗 [NewStacking] EC2 스태킹 관계 생성:" , target . type ) ;
229+ // 3단계: Subnet 검증 - 겹침 면적 기반 선택
230+ const subnetOverlapData = subnets . map ( subnet => {
231+ const ec2SizeX = newBlock . size ?. [ 0 ] || 1 ;
232+ const ec2SizeZ = newBlock . size ?. [ 2 ] || 1 ;
233+ const subnetSizeX = subnet . size ?. [ 0 ] || 3 ;
234+ const subnetSizeZ = subnet . size ?. [ 2 ] || 3 ;
235+
236+ // X축 겹침 계산
237+ const ec2Left = newBlock . position . x - ec2SizeX / 2 ;
238+ const ec2Right = newBlock . position . x + ec2SizeX / 2 ;
239+ const subnetLeft = subnet . position . x - subnetSizeX / 2 ;
240+ const subnetRight = subnet . position . x + subnetSizeX / 2 ;
241+
242+ const xOverlapStart = Math . max ( ec2Left , subnetLeft ) ;
243+ const xOverlapEnd = Math . min ( ec2Right , subnetRight ) ;
244+ const xOverlap = Math . max ( 0 , xOverlapEnd - xOverlapStart ) ;
245+
246+ // Z축 겹침 계산
247+ const ec2Front = newBlock . position . z - ec2SizeZ / 2 ;
248+ const ec2Back = newBlock . position . z + ec2SizeZ / 2 ;
249+ const subnetFront = subnet . position . z - subnetSizeZ / 2 ;
250+ const subnetBack = subnet . position . z + subnetSizeZ / 2 ;
251+
252+ const zOverlapStart = Math . max ( ec2Front , subnetFront ) ;
253+ const zOverlapEnd = Math . min ( ec2Back , subnetBack ) ;
254+ const zOverlap = Math . max ( 0 , zOverlapEnd - zOverlapStart ) ;
255+
256+ // 겹침 면적
257+ const overlapArea = xOverlap * zOverlap ;
258+ const ec2Area = ec2SizeX * ec2SizeZ ;
259+ const overlapRatio = ec2Area > 0 ? overlapArea / ec2Area : 0 ;
260+
261+ // Y축 검증
262+ const yValid = validateStacking ( newBlock , subnet ) ;
263+
264+ console . log ( `🎯 [NewStacking] Subnet 겹침 분석: ${ subnet . type } ` , {
265+ subnetId : subnet . id . substring ( 0 , 8 ) ,
266+ ec2Pos : `(${ newBlock . position . x . toFixed ( 1 ) } , ${ newBlock . position . z . toFixed ( 1 ) } )` ,
267+ subnetPos : `(${ subnet . position . x . toFixed ( 1 ) } , ${ subnet . position . z . toFixed ( 1 ) } )` ,
268+ xOverlap : xOverlap . toFixed ( 2 ) ,
269+ zOverlap : zOverlap . toFixed ( 2 ) ,
270+ overlapRatio : ( overlapRatio * 100 ) . toFixed ( 1 ) + '%' ,
271+ yValid
247272 } ) ;
248273
249- // 위치 조정은 주요 대상(Subnet 우선)으로
250- const primaryTarget = selectStackingTargetByPriority (
251- newBlock ,
252- closeTargets
253- ) ;
254- if ( forcePosition && primaryTarget ) {
255- const stackedPosition = calculateStackedPosition (
256- newBlock ,
257- primaryTarget
258- ) ;
259- moveBlock ( newBlock . id , stackedPosition ) ;
260- console . log (
261- "📍 [NewStacking] EC2 위치 조정됨 (주요 대상:" ,
262- primaryTarget . type ,
263- ")"
264- ) ;
265- } else {
266- console . log ( "🎯 [NewStacking] EC2 사용자 위치 유지" ) ;
267- }
268- } else {
269- // 다른 블록 타입은 기존 방식 (단일 대상)
270- const targetBlock = selectStackingTargetByPriority (
271- newBlock ,
272- potentialTargets
273- ) ;
274+ return { subnet, overlapRatio, yValid } ;
275+ } ) ;
274276
275- if ( targetBlock ) {
276- console . log ( "🔗 [NewStacking] 스태킹 대상 발견:" , targetBlock . type ) ;
277+ // 겹침 면적이 30% 이상이고 Y축 검증 통과한 Subnet 필터링
278+ const validSubnets = subnetOverlapData
279+ . filter ( data => data . overlapRatio >= 0.3 && data . yValid )
280+ . sort ( ( a , b ) => b . overlapRatio - a . overlapRatio ) ;
277281
278- // 스태킹 관계 생성
279- createStackingRelation ( newBlock . id , targetBlock . id , allBlocks ) ;
282+ console . log ( "✅ [NewStacking] 최종 스태킹 타겟:" , {
283+ volumes : validVolumes . map ( v => `${ v . type } (${ v . id . substring ( 0 , 8 ) } )` ) ,
284+ subnets : validSubnets . map ( s => `${ s . subnet . type } (${ s . subnet . id . substring ( 0 , 8 ) } ) - ${ ( s . overlapRatio * 100 ) . toFixed ( 1 ) } %` )
285+ } ) ;
280286
281- // 위치 조정 (옵션)
282- if ( forcePosition ) {
283- const stackedPosition = calculateStackedPosition (
284- newBlock ,
285- targetBlock
286- ) ;
287- moveBlock ( newBlock . id , stackedPosition ) ;
288- console . log ( "📍 [NewStacking] 위치 강제 조정됨" ) ;
289- } else {
290- console . log ( "🎯 [NewStacking] 사용자 위치 유지" ) ;
291- }
292- }
287+ // 4단계: 스태킹 관계 생성
288+ validVolumes . forEach ( vol => {
289+ createStackingRelation ( newBlock . id , vol . id , allBlocks ) ;
290+ console . log ( "🔗 [NewStacking] 부트볼륨 연결:" , vol . type ) ;
291+ } ) ;
292+
293+ if ( validSubnets . length > 0 ) {
294+ const bestSubnet = validSubnets [ 0 ] . subnet ;
295+ createStackingRelation ( newBlock . id , bestSubnet . id , allBlocks ) ;
296+ console . log ( "🔗 [NewStacking] Subnet 연결:" , bestSubnet . type , `(${ ( validSubnets [ 0 ] . overlapRatio * 100 ) . toFixed ( 1 ) } %)` ) ;
293297 }
294298
299+ // 위치 조정 없음 (사용자 드래그 위치 유지)
300+ console . log ( "🎯 [NewStacking] EC2 사용자 위치 유지" ) ;
301+
295302 // 즉시 연결 업데이트
296303 const derivedConnections = deriveConnectionsFromStacking ( allBlocks ) ;
297304 const nonStackingConnections = connections . filter (
@@ -306,7 +313,47 @@ function ProjectEditorPage() {
306313 "개"
307314 ) ;
308315 } else {
309- console . log ( "ℹ️ [NewStacking] 스태킹 대상 없음" ) ;
316+ // ===== 일반 블록 로직 =====
317+ const potentialTargets = allBlocks
318+ . filter ( ( block ) => block . id !== newBlock . id )
319+ . filter ( ( block ) => canStack ( newBlock . type , block . type ) )
320+ . filter ( ( block ) => validateStacking ( newBlock , block ) ) ;
321+
322+ if ( potentialTargets . length > 0 ) {
323+ const targetBlock = selectStackingTargetByPriority (
324+ newBlock ,
325+ potentialTargets
326+ ) ;
327+
328+ if ( targetBlock ) {
329+ console . log ( "🔗 [NewStacking] 스태킹 대상 발견:" , targetBlock . type ) ;
330+ createStackingRelation ( newBlock . id , targetBlock . id , allBlocks ) ;
331+
332+ if ( forcePosition ) {
333+ const stackedPosition = calculateStackedPosition ( newBlock , targetBlock ) ;
334+ moveBlock ( newBlock . id , stackedPosition ) ;
335+ console . log ( "📍 [NewStacking] 위치 강제 조정됨" ) ;
336+ } else {
337+ console . log ( "🎯 [NewStacking] 사용자 위치 유지" ) ;
338+ }
339+ }
340+
341+ // 즉시 연결 업데이트
342+ const derivedConnections = deriveConnectionsFromStacking ( allBlocks ) ;
343+ const nonStackingConnections = connections . filter (
344+ ( conn ) => ! conn . properties ?. stackConnection
345+ ) ;
346+ const allConnections = [ ...nonStackingConnections , ...derivedConnections ] ;
347+ setConnections ( allConnections ) ;
348+
349+ console . log (
350+ "✅ [NewStacking] 스태킹 완료 + 연결 업데이트:" ,
351+ derivedConnections . length ,
352+ "개"
353+ ) ;
354+ } else {
355+ console . log ( "ℹ️ [NewStacking] 스태킹 대상 없음" ) ;
356+ }
310357 }
311358 } ;
312359
0 commit comments