From c9f0a70385a160a6e8be31d267a57dedae4956e1 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Fri, 30 Jan 2026 15:51:46 +0900 Subject: [PATCH] =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EC=84=A4=EC=B9=98=20?= =?UTF-8?q?=EC=A0=9C=EB=8C=80=EB=A1=9C=20=EC=95=88=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=ED=98=84=EC=83=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/module/useModuleBasicSetting.js | 145 ++++++++++++++++------ 1 file changed, 109 insertions(+), 36 deletions(-) diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index 45fd8a53..ce7069ac 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -17,7 +17,7 @@ import { import { calculateVisibleModuleHeight, getDegreeByChon, polygonToTurfPolygon, rectToPolygon, toFixedWithoutRounding } from '@/util/canvas-util' import '@/util/fabric-extensions' // fabric 객체들에 getCurrentPoints 메서드 추가 import { basicSettingState, roofDisplaySelector } from '@/store/settingAtom' -import offsetPolygon, { calculateAngle, createLinesFromPolygon, cleanSelfIntersectingPolygon } from '@/util/qpolygon-utils' +import offsetPolygon, { calculateAngle, cleanSelfIntersectingPolygon, createLinesFromPolygon } from '@/util/qpolygon-utils' import { QPolygon } from '@/components/fabric/QPolygon' import { useEvent } from '@/hooks/useEvent' import { BATCH_TYPE, LINE_TYPE, MODULE_SETUP_TYPE, POLYGON_TYPE } from '@/common/common' @@ -2105,7 +2105,7 @@ export function useModuleBasicSetting(tabNum) { } //흐름 방향이 남쪽(아래) - const downFlowSetupModule = (maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) => { + const downFlowSetupModule = (maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer, symmetricWidthRef) => { let setupModule = [] const trestleDetailData = moduleSetupSurface.trestleDetail @@ -2184,6 +2184,11 @@ export function useModuleBasicSetting(tabNum) { calcAreaHeight = isNaN(calcAreaHeight) ? moduleSetupSurface.height : calcAreaHeight let calcModuleHeightCount = calcAreaHeight / (height + intvVer) + // 대칭 지붕을 위해 south의 calcAreaWidth 저장 (north에서 참조) + if (symmetricWidthRef && moduleIndex === 0) { + symmetricWidthRef.south = calcAreaWidth + } + if (type === MODULE_SETUP_TYPE.LAYOUT) { calcModuleWidthCount = layoutCol > calcModuleWidthCount ? calcModuleWidthCount : layoutCol calcModuleHeightCount = layoutRow @@ -2287,7 +2292,7 @@ export function useModuleBasicSetting(tabNum) { } } - const topFlowSetupModule = (maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) => { + const topFlowSetupModule = (maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer, symmetricWidthRef) => { let setupModule = [] const trestleDetailData = moduleSetupSurface.trestleDetail @@ -2366,9 +2371,22 @@ export function useModuleBasicSetting(tabNum) { //육지붕이 아닐때만 넣는다 육지붕일땐 클릭 이벤트에 별도로 넣어놓음 const moduleArray = [] - let calcAreaWidth = flowLines.right.x1 - flowLines.left.x1 //오른쪽 x에서 왼쪽 x를 뺀 가운데를 찾는 로직 + // 북쪽: 남쪽과 동일한 방식으로 계산 (대칭을 위해) + let calcAreaWidth = Math.abs(flowLines.right.x1 - flowLines.left.x1) //오른쪽 x에서 왼쪽 x를 뺀 가운데를 찾는 로직 + + // 대칭 지붕: south의 calcAreaWidth가 있고 north의 값이 south보다 10% 이상 작으면 south 값 사용 + if (symmetricWidthRef?.south && calcAreaWidth < symmetricWidthRef.south * 0.9) { + // flowLines 좌표도 보정 (중심점 유지하면서 너비 확장) + const center = (flowLines.right.x1 + flowLines.left.x1) / 2 + const halfWidth = symmetricWidthRef.south / 2 + flowLines.left.x1 = center - halfWidth + flowLines.right.x1 = center + halfWidth + + calcAreaWidth = symmetricWidthRef.south + } + let calcModuleWidthCount = calcAreaWidth / (width + intvHor) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 - let calcAreaHeight = flowLines.bottom.y1 - flowLines.top.y1 + let calcAreaHeight = Math.abs(flowLines.bottom.y1 - flowLines.top.y1) let calcModuleHeightCount = calcAreaHeight / (height + intvVer) //단수지정 자동이면 @@ -2469,7 +2487,7 @@ export function useModuleBasicSetting(tabNum) { //남, 북과 같은 로직으로 적용하려면 좌우는 열 -> 행 으로 그려야함 //변수명은 bottom 기준으로 작성하여 동일한 방향으로 진행한다 - const leftFlowSetupModule = (maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) => { + const leftFlowSetupModule = (maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer, symmetricWidthRef) => { let setupModule = [] const trestleDetailData = moduleSetupSurface.trestleDetail //가대 상세 데이터 @@ -2557,6 +2575,11 @@ export function useModuleBasicSetting(tabNum) { let calcAreaHeight = Math.abs(flowLines.right.x1 - flowLines.left.x1) let calcModuleHeightCount = calcAreaHeight / (width + intvVer) + // 대칭 지붕을 위해 west의 calcAreaWidth 저장 (east에서 참조) + if (symmetricWidthRef && moduleIndex === 0) { + symmetricWidthRef.west = calcAreaWidth + } + //단수지정 자동이면 if (type === MODULE_SETUP_TYPE.LAYOUT) { calcModuleWidthCount = layoutCol > calcModuleWidthCount ? calcModuleWidthCount : layoutCol @@ -2655,7 +2678,7 @@ export function useModuleBasicSetting(tabNum) { } } - const rightFlowSetupModule = (maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) => { + const rightFlowSetupModule = (maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer, symmetricWidthRef) => { let setupModule = [] const trestleDetailData = moduleSetupSurface.trestleDetail //가대 상세 데이터 @@ -2736,9 +2759,22 @@ export function useModuleBasicSetting(tabNum) { //육지붕이 아닐때만 넣는다 육지붕일땐 클릭 이벤트에 별도로 넣어놓음 const moduleArray = [] - let calcAreaWidth = flowLines.bottom.y1 - flowLines.top.y1 //아래에서 y에서 위를 y를 뺀 가운데를 찾는 로직 + // 동쪽: 서쪽과 동일한 방식으로 계산 (대칭을 위해) + let calcAreaWidth = Math.abs(flowLines.bottom.y1 - flowLines.top.y1) //아래에서 y에서 위를 y를 뺀 가운데를 찾는 로직 + + // 대칭 지붕: west의 calcAreaWidth가 있고 east의 값이 west보다 10% 이상 작으면 west 값 사용 + if (symmetricWidthRef?.west && calcAreaWidth < symmetricWidthRef.west * 0.9) { + // flowLines 좌표도 보정 (중심점 유지하면서 높이 확장) + const center = (flowLines.bottom.y1 + flowLines.top.y1) / 2 + const halfHeight = symmetricWidthRef.west / 2 + flowLines.top.y1 = center - halfHeight + flowLines.bottom.y1 = center + halfHeight + + calcAreaWidth = symmetricWidthRef.west + } + let calcModuleWidthCount = calcAreaWidth / (height + intvHor) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 - let calcAreaHeight = flowLines.right.x1 - flowLines.left.x1 + let calcAreaHeight = Math.abs(flowLines.right.x1 - flowLines.left.x1) let calcModuleHeightCount = calcAreaHeight / (width + intvVer) //단수지정 자동이면 @@ -2748,15 +2784,14 @@ export function useModuleBasicSetting(tabNum) { } let calcMaxModuleWidthCount = calcModuleWidthCount > moduleMaxCols ? moduleMaxCols : calcModuleWidthCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 - // let totalModuleWidthCount = isChidori ? Math.abs(calcMaxModuleWidthCount) : Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 - let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 + let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) let calcStartPoint = flowLines.top.type === 'flat' ? (calcAreaWidth - totalModuleWidthCount * height) / 2 : 0 //반씩 나눠서 중앙에 맞춤 left 높이 기준으로 양변이 직선일때만 가운데 정렬 - let startPointX = flowLines.bottom.y2 - calcStartPoint //시작점을 만든다 + let startPointX = flowLines.bottom.y1 - calcStartPoint //시작점을 만든다 //근데 양변이 곡선이면 중앙에 맞추기 위해 아래와 위의 길이를 재서 모듈의 길이를 나눠서 들어갈수 있는 갯수가 동일하면 가운데로 정렬 시킨다 if (flowLines.top.type === 'curve' && flowLines.bottom.type === 'curve') { - startPointX = flowLines.bottom.y2 - (calcAreaWidth - totalModuleWidthCount * height) / 2 + startPointX = flowLines.bottom.y1 - (calcAreaWidth - totalModuleWidthCount * height) / 2 } let heightMargin = 0 @@ -2843,7 +2878,18 @@ export function useModuleBasicSetting(tabNum) { } } - moduleSetupSurfaces.forEach((moduleSetupSurface, index) => { + // 대칭 지붕을 위한 calcAreaWidth 공유 객체 (south→north, west→east) + const symmetricWidthRef = { south: null, west: null } + + // 대칭 보정을 위해 south/west가 north/east보다 먼저 처리되도록 정렬 + const directionOrder = { south: 0, west: 1, north: 2, east: 3 } + const sortedModuleSetupSurfaces = [...moduleSetupSurfaces].sort((a, b) => { + const orderA = directionOrder[a.direction] ?? 4 + const orderB = directionOrder[b.direction] ?? 4 + return orderA - orderB + }) + + sortedModuleSetupSurfaces.forEach((moduleSetupSurface, index) => { moduleSetupSurface.fire('mousedown') const moduleSetupArray = [] @@ -2869,30 +2915,30 @@ export function useModuleBasicSetting(tabNum) { if (setupLocation === 'eaves') { // 흐름방향이 남쪽일때 if (moduleSetupSurface.direction === 'south') { - downFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) + downFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer, symmetricWidthRef) } if (moduleSetupSurface.direction === 'west') { - leftFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) + leftFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer, symmetricWidthRef) } if (moduleSetupSurface.direction === 'east') { - rightFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) + rightFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer, symmetricWidthRef) } if (moduleSetupSurface.direction === 'north') { - topFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) + topFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer, symmetricWidthRef) } } else if (setupLocation === 'ridge') { //용마루 if (moduleSetupSurface.direction === 'south') { - topFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) + topFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer, symmetricWidthRef) } if (moduleSetupSurface.direction === 'west') { - rightFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) + rightFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer, symmetricWidthRef) } if (moduleSetupSurface.direction === 'east') { - leftFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) + leftFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer, symmetricWidthRef) } if (moduleSetupSurface.direction === 'north') { - downFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer) + downFlowSetupModule(maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, intvHor, intvVer, symmetricWidthRef) } } @@ -3072,13 +3118,26 @@ export function useModuleBasicSetting(tabNum) { type: 'flat', } } else { - rtnObj = { - target: index === 0 ? 'bottom' : 'top', - x1: pointX1, - y1: pointY1, - x2: pointX2, - y2: pointY2, - type: 'curve', + // NaN 체크: offset이 너무 커서 꼭짓점을 넘어가면 NaN 발생 + if (isNaN(pointX1) || isNaN(pointX2) || isNaN(pointY1) || isNaN(pointY2)) { + // NaN이면 꼭짓점 좌표 사용 (모듈 설치 영역 없음) + rtnObj = { + target: index === 0 ? 'bottom' : 'top', + x1: center.x1, + y1: center.y1, + x2: center.x1, + y2: center.y1, + type: 'curve', + } + } else { + rtnObj = { + target: index === 0 ? 'bottom' : 'top', + x1: pointX1, + y1: pointY1, + x2: pointX2, + y2: pointY2, + type: 'curve', + } } } @@ -3204,13 +3263,26 @@ export function useModuleBasicSetting(tabNum) { type: 'flat', } } else { - rtnObj = { - target: index === 0 ? 'left' : 'right', - x1: pointX1, - y1: pointY1, - x2: pointX2, - y2: pointY2, - type: 'curve', + // NaN 체크: offset이 너무 커서 꼭짓점을 넘어가면 NaN 발생 + if (isNaN(pointX1) || isNaN(pointX2) || isNaN(pointY1) || isNaN(pointY2)) { + // NaN이면 꼭짓점 좌표 사용 (모듈 설치 영역 없음) + rtnObj = { + target: index === 0 ? 'left' : 'right', + x1: center.x1, + y1: center.y1, + x2: center.x1, + y2: center.y1, + type: 'curve', + } + } else { + rtnObj = { + target: index === 0 ? 'left' : 'right', + x1: pointX1, + y1: pointY1, + x2: pointX2, + y2: pointY2, + type: 'curve', + } } } rtnObjArray.push(rtnObj) @@ -4101,6 +4173,7 @@ export function useModuleBasicSetting(tabNum) { left: leftRightFlowLine(moduleSetupSurface, length).find((obj) => obj.target === 'left'), right: leftRightFlowLine(moduleSetupSurface, length).find((obj) => obj.target === 'right'), } + return flowLines }