From 1524766b7efb2a40ebfdbe19771b07d3b7824ca4 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 31 Dec 2025 13:52:50 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=EC=99=B8=EB=B2=BD=EC=84=A0=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EC=B2=98=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/common.js | 1 + src/hooks/roofcover/useMovementSetting.js | 206 ++++++++++-------- src/hooks/roofcover/useOuterLineWall.js | 1 + .../roofcover/useRoofShapePassivitySetting.js | 5 +- src/hooks/roofcover/useRoofShapeSetting.js | 6 + src/hooks/surface/useSurfaceShapeBatch.js | 1 + 6 files changed, 130 insertions(+), 90 deletions(-) diff --git a/src/common/common.js b/src/common/common.js index 76632014..c0fe3701 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -222,6 +222,7 @@ export const SAVE_KEY = [ 'skeletonLines', 'skeleton', 'viewportTransform', + 'outerLineFix', ] export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype, fabric.Group.prototype] diff --git a/src/hooks/roofcover/useMovementSetting.js b/src/hooks/roofcover/useMovementSetting.js index e74a59e3..92f2e2c0 100644 --- a/src/hooks/roofcover/useMovementSetting.js +++ b/src/hooks/roofcover/useMovementSetting.js @@ -142,6 +142,15 @@ export function useMovementSetting(id) { } }, []) + useEffect(() => { + const roofs = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) + if (roofs.length === 0) { + swalFire({ text: getMessage('roof.line.not.found') }) + closePopup(id) + return + } + }, []) + /** object 선택이 변경될 때 처리*/ useEffect(() => { if (FOLLOW_LINE_REF.current != null) { @@ -186,7 +195,6 @@ export function useMovementSetting(id) { canvas.renderAll() }, [currentObject]) - const clearRef = () => { if (type === TYPE.FLOW_LINE) { // 안전한 ref 접근 @@ -258,23 +266,25 @@ export function useMovementSetting(id) { let value = '' let direction = '' - if (Math.abs(target.y1 - target.y2) < 0.5) { // 수평 라인 + if (Math.abs(target.y1 - target.y2) < 0.5) { + // 수평 라인 value = Big(targetTop).minus(currentY).times(10).round(0) // 방향 감지 if (value.toNumber() > 0) { - direction = 'up' // 마우스가 라인 위쪽에 있음 (위로 움직임) + direction = 'up' // 마우스가 라인 위쪽에 있음 (위로 움직임) } else if (value.toNumber() < 0) { - direction = 'down' // 마우스가 라인 아래쪽에 있음 (아래로 움직임) + direction = 'down' // 마우스가 라인 아래쪽에 있음 (아래로 움직임) } - } else { // 수직 라인 + } else { + // 수직 라인 value = Big(targetLeft).minus(currentX).times(10).round(0).neg() // 방향 감지 if (value.toNumber() > 0) { - direction = 'right' // 마우스가 라인 오른쪽에 있음 (오른쪽으로 움직임) + direction = 'right' // 마우스가 라인 오른쪽에 있음 (오른쪽으로 움직임) } else if (value.toNumber() < 0) { - direction = 'left' // 마우스가 라인 왼쪽에 있음 (왼쪽으로 움직임) + direction = 'left' // 마우스가 라인 왼쪽에 있음 (왼쪽으로 움직임) } } @@ -312,37 +322,35 @@ export function useMovementSetting(id) { const midY = Big(target.y1).plus(target.y2).div(2) const wall = canvas.getObjects().find((obj) => obj.id === target.attributes.wallId) - - const result = getSelectLinePosition(wall, target, { - testDistance: 5, // 테스트 거리 - debug: true // 디버깅 로그 출력 - }); + testDistance: 5, // 테스트 거리 + debug: true, // 디버깅 로그 출력 + }) //console.log("1111litarget:::::", target); //console.log("1111linePosition:::::", result.position); // 'top', 'bottom', 'left', 'right' - let linePosition = result.position; -//console.log("1111linePosition:::::", direction, linePosition); + let linePosition = result.position + //console.log("1111linePosition:::::", direction, linePosition); - if (target.y1 === target.y2) { //수평벽 + if (target.y1 === target.y2) { + //수평벽 const setRadioStates = (isUp) => { if (UP_DOWN_REF.UP_RADIO_REF.current) { - UP_DOWN_REF.UP_RADIO_REF.current.checked = isUp; + UP_DOWN_REF.UP_RADIO_REF.current.checked = isUp } if (UP_DOWN_REF.DOWN_RADIO_REF.current) { - UP_DOWN_REF.DOWN_RADIO_REF.current.checked = !isUp; + UP_DOWN_REF.DOWN_RADIO_REF.current.checked = !isUp } - }; - - if (linePosition === 'top') { - setRadioStates(value.s !== -1); - } else if (linePosition === 'bottom') { - setRadioStates(value.s !== 1); } - if(direction === 'up') { + if (linePosition === 'top') { + setRadioStates(value.s !== -1) + } else if (linePosition === 'bottom') { + setRadioStates(value.s !== 1) + } + if (direction === 'up') { } /* checkPoint = { x: midX.toNumber(), y: midY.plus(10).toNumber() } @@ -367,20 +375,19 @@ export function useMovementSetting(id) { } */ } else { - const setRadioStates = (isUp) => { if (UP_DOWN_REF.UP_RADIO_REF.current) { - UP_DOWN_REF.UP_RADIO_REF.current.checked = isUp; + UP_DOWN_REF.UP_RADIO_REF.current.checked = isUp } if (UP_DOWN_REF.DOWN_RADIO_REF.current) { - UP_DOWN_REF.DOWN_RADIO_REF.current.checked = !isUp; + UP_DOWN_REF.DOWN_RADIO_REF.current.checked = !isUp } - }; + } if (linePosition === 'left') { - setRadioStates(value.s !== 1); + setRadioStates(value.s !== 1) } else if (linePosition === 'right') { - setRadioStates(value.s !== -1); + setRadioStates(value.s !== -1) } /* checkPoint = { x: midX.plus(10).toNumber(), y: midY.toNumber() } @@ -407,11 +414,8 @@ export function useMovementSetting(id) { */ } } - } - - const mouseDownEvent = (e) => { canvas .getObjects() @@ -460,7 +464,6 @@ export function useMovementSetting(id) { canvas.renderAll() } - const target = selectedObject.current !== null ? selectedObject.current : CONFIRM_LINE_REF.current?.target if (!target) return @@ -468,23 +471,23 @@ export function useMovementSetting(id) { const roof = canvas.getObjects().find((obj) => obj.id === roofId) // 현이동, 동이동 추가 - let flPointValue = FLOW_LINE_REF.POINTER_INPUT_REF.current?.value ?? 0; - let flFilledValue = FLOW_LINE_REF.FILLED_INPUT_REF.current?.value ?? 0; - flPointValue = (flFilledValue > 0 || flFilledValue < 0) ? flFilledValue : flPointValue; + let flPointValue = FLOW_LINE_REF.POINTER_INPUT_REF.current?.value ?? 0 + let flFilledValue = FLOW_LINE_REF.FILLED_INPUT_REF.current?.value ?? 0 + flPointValue = flFilledValue > 0 || flFilledValue < 0 ? flFilledValue : flPointValue const moveFlowLine = typeRef.current === TYPE.FLOW_LINE ? flPointValue : 0 - let udPointValue = UP_DOWN_REF.POINTER_INPUT_REF.current?.value ?? 0; - let udFilledValue = UP_DOWN_REF.FILLED_INPUT_REF.current?.value ?? 0; - udPointValue = udFilledValue > 0 ? udFilledValue : udPointValue; + let udPointValue = UP_DOWN_REF.POINTER_INPUT_REF.current?.value ?? 0 + let udFilledValue = UP_DOWN_REF.FILLED_INPUT_REF.current?.value ?? 0 + udPointValue = udFilledValue > 0 ? udFilledValue : udPointValue const moveUpDown = typeRef.current === TYPE.UP_DOWN ? udPointValue : 0 - roof.moveFlowLine = parseInt(moveFlowLine, 10) || 0; - roof.moveUpDown = parseInt(moveUpDown, 10) || 0; - roof.moveDirect = ""; + roof.moveFlowLine = parseInt(moveFlowLine, 10) || 0 + roof.moveUpDown = parseInt(moveUpDown, 10) || 0 + roof.moveDirect = '' roof.moveSelectLine = target //console.log("target::::", target, roof.moveSelectLine) const wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId) const baseLines = wall.baseLines - let centerPoint = wall.getCenterPoint(); + let centerPoint = wall.getCenterPoint() let targetBaseLines = [] let isGableRoof if (typeRef.current === TYPE.FLOW_LINE) { @@ -522,9 +525,19 @@ export function useMovementSetting(id) { return minX <= line.x1 && line.x1 <= maxX && minX <= line.x2 && line.x2 <= maxX }) if (isGableRoof && currentBaseLines.length > 0) { - currentBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(line.y1).minus(target.y1).abs().toNumber() })) + currentBaseLines.forEach((line) => + targetBaseLines.push({ + line, + distance: Big(line.y1).minus(target.y1).abs().toNumber(), + }), + ) } else { - checkBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(target.y1).minus(line.y1).abs().toNumber() })) + checkBaseLines.forEach((line) => + targetBaseLines.push({ + line, + distance: Big(target.y1).minus(line.y1).abs().toNumber(), + }), + ) } baseLines .filter((line) => line.y1 === line.y2 && line.y1 < target.y1) @@ -538,9 +551,19 @@ export function useMovementSetting(id) { return minX <= line.x1 && line.x1 <= maxX && minX <= line.x2 && line.x2 <= maxX }) if (isGableRoof && currentBaseLines.length > 0) { - currentBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(line.y1).minus(target.y1).abs().toNumber() })) + currentBaseLines.forEach((line) => + targetBaseLines.push({ + line, + distance: Big(line.y1).minus(target.y1).abs().toNumber(), + }), + ) } else { - checkBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(line.y1).minus(target.y1).abs().toNumber() })) + checkBaseLines.forEach((line) => + targetBaseLines.push({ + line, + distance: Big(line.y1).minus(target.y1).abs().toNumber(), + }), + ) } break case 'right': @@ -551,9 +574,19 @@ export function useMovementSetting(id) { return minY <= line.y1 && line.y1 <= maxY && minY <= line.y2 && line.y2 <= maxY }) if (isGableRoof && currentBaseLines.length > 0) { - currentBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(line.x1).minus(target.x1).abs().toNumber() })) + currentBaseLines.forEach((line) => + targetBaseLines.push({ + line, + distance: Big(line.x1).minus(target.x1).abs().toNumber(), + }), + ) } else { - checkBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(target.x1).minus(line.x1).abs().toNumber() })) + checkBaseLines.forEach((line) => + targetBaseLines.push({ + line, + distance: Big(target.x1).minus(line.x1).abs().toNumber(), + }), + ) } break case 'left': @@ -564,9 +597,19 @@ export function useMovementSetting(id) { return minY <= line.y1 && line.y1 <= maxY && minY <= line.y2 && line.y2 <= maxY }) if (isGableRoof && currentBaseLines.length > 0) { - currentBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(line.x1).minus(target.x1).abs().toNumber() })) + currentBaseLines.forEach((line) => + targetBaseLines.push({ + line, + distance: Big(line.x1).minus(target.x1).abs().toNumber(), + }), + ) } else { - checkBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(target.x1).minus(line.x1).abs().toNumber() })) + checkBaseLines.forEach((line) => + targetBaseLines.push({ + line, + distance: Big(target.x1).minus(line.x1).abs().toNumber(), + }), + ) } break } @@ -576,18 +619,18 @@ export function useMovementSetting(id) { } // Remove duplicate lines - const uniqueLines = new Map(); - targetBaseLines = targetBaseLines.filter(item => { - const key = `${item.line.x1},${item.line.y1},${item.line.x2},${item.line.y2}`; + const uniqueLines = new Map() + targetBaseLines = targetBaseLines.filter((item) => { + const key = `${item.line.x1},${item.line.y1},${item.line.x2},${item.line.y2}` if (!uniqueLines.has(key)) { - uniqueLines.set(key, true); - return true; + uniqueLines.set(key, true) + return true } - return false; - }); + return false + }) // Sort by distance - targetBaseLines.sort((a, b) => a.distance - b.distance); + targetBaseLines.sort((a, b) => a.distance - b.distance) targetBaseLines = targetBaseLines.filter((line) => line.distance === targetBaseLines[0].distance) if (isGableRoof) { @@ -621,27 +664,22 @@ export function useMovementSetting(id) { let value if (typeRef.current === TYPE.FLOW_LINE) { value = (() => { - const filledValue = FLOW_LINE_REF.FILLED_INPUT_REF.current?.value; - const pointerValue = FLOW_LINE_REF.POINTER_INPUT_REF.current?.value; + const filledValue = FLOW_LINE_REF.FILLED_INPUT_REF.current?.value + const pointerValue = FLOW_LINE_REF.POINTER_INPUT_REF.current?.value if (filledValue && !isNaN(filledValue) && filledValue.trim() !== '') { - return Big(filledValue).times(2); + return Big(filledValue).times(2) } else if (pointerValue && !isNaN(pointerValue) && pointerValue.trim() !== '') { - return Big(pointerValue).times(2); + return Big(pointerValue).times(2) } - return Big(0); // 기본값으로 0 반환 또는 다른 적절한 기본값 - })(); + return Big(0) // 기본값으로 0 반환 또는 다른 적절한 기본값 + })() if (Math.abs(target.y1 - target.y2) < 0.5) { value = value.neg() } } else { - console.log("error::", UP_DOWN_REF.POINTER_INPUT_REF.current.value) - value = Big( - (UP_DOWN_REF?.FILLED_INPUT_REF?.current?.value?.trim() || - UP_DOWN_REF?.POINTER_INPUT_REF?.current?.value?.trim() || - '0' - ) - ); + console.log('error::', UP_DOWN_REF.POINTER_INPUT_REF.current.value) + value = Big(UP_DOWN_REF?.FILLED_INPUT_REF?.current?.value?.trim() || UP_DOWN_REF?.POINTER_INPUT_REF?.current?.value?.trim() || '0') const midX = Big(target.x1).plus(target.x2).div(2) const midY = Big(target.y1).plus(target.y2).div(2) @@ -665,17 +703,16 @@ export function useMovementSetting(id) { // console.log("2222저장된 moveSelectLine:", roof.moveSelectLine); // console.log("222wall::::", wall.points) const result = getSelectLinePosition(wall, target, { - testDistance: 5, // 테스트 거리 - debug: true // 디버깅 로그 출력 - }); + testDistance: 5, // 테스트 거리 + debug: true, // 디버깅 로그 출력 + }) //console.log("2222linePosition:::::", result.position); //console.log("222moveDirect:::::", roof.moveDirect); + // 디버깅용 분류 결과 확인 -// 디버깅용 분류 결과 확인 - - let linePosition = result.position; + let linePosition = result.position roof.movePosition = linePosition value = value.div(10) targetBaseLines @@ -684,15 +721,14 @@ export function useMovementSetting(id) { const currentLine = target.line //console.log("linePosition::::::::::::::", linePosition) - if (UP_DOWN_REF?.DOWN_RADIO_REF?.current?.checked ){ + if (UP_DOWN_REF?.DOWN_RADIO_REF?.current?.checked) { //position확인 - if(linePosition === 'bottom' || linePosition === 'right') { + if (linePosition === 'bottom' || linePosition === 'right') { //console.log("1value::::::::::::::", value.toString()) value = value.neg() - } - }else { - if(linePosition === 'top' || linePosition === 'left') { + } else { + if (linePosition === 'top' || linePosition === 'left') { //console.log("1value::::::::::::::", value.toString()) value = value.neg() } @@ -753,7 +789,6 @@ export function useMovementSetting(id) { // javascript - return { TYPE, closePopup, @@ -765,4 +800,3 @@ export function useMovementSetting(id) { handleSave, } } - diff --git a/src/hooks/roofcover/useOuterLineWall.js b/src/hooks/roofcover/useOuterLineWall.js index ebe1d8ad..dd304865 100644 --- a/src/hooks/roofcover/useOuterLineWall.js +++ b/src/hooks/roofcover/useOuterLineWall.js @@ -251,6 +251,7 @@ export function useOuterLineWall(id, propertiesId) { removeAllDocumentEventListeners() canvas?.renderAll() setOuterLineFix(true) + canvas.outerLineFix = true closePopup(id) ccwCheck() addPopup(propertiesId, 1, ) diff --git a/src/hooks/roofcover/useRoofShapePassivitySetting.js b/src/hooks/roofcover/useRoofShapePassivitySetting.js index 241d2fac..ac36779c 100644 --- a/src/hooks/roofcover/useRoofShapePassivitySetting.js +++ b/src/hooks/roofcover/useRoofShapePassivitySetting.js @@ -7,7 +7,6 @@ import { useEvent } from '@/hooks/useEvent' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import { useMode } from '@/hooks/useMode' import { usePolygon } from '@/hooks/usePolygon' -import { outerLineFixState } from '@/store/outerLineAtom' import { useSwal } from '@/hooks/useSwal' import { usePopup } from '@/hooks/usePopup' import { getChonByDegree } from '@/util/canvas-util' @@ -46,11 +45,9 @@ export function useRoofShapePassivitySetting(id) { { id: 3, name: getMessage('windage'), type: TYPES.SHED }, ] - const outerLineFix = useRecoilValue(outerLineFixState) - useEffect(() => { const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') - if (!outerLineFix || outerLines.length === 0) { + if (!canvas.outerLineFix || outerLines.length === 0) { swalFire({ text: getMessage('wall.line.not.found') }) closePopup(id) return diff --git a/src/hooks/roofcover/useRoofShapeSetting.js b/src/hooks/roofcover/useRoofShapeSetting.js index 9e1d00ff..f5c84f5b 100644 --- a/src/hooks/roofcover/useRoofShapeSetting.js +++ b/src/hooks/roofcover/useRoofShapeSetting.js @@ -81,6 +81,12 @@ export function useRoofShapeSetting(id) { }, [jerkinHeadPitch]) useEffect(() => { + const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + if (!canvas.outerLineFix || outerLines.length === 0) { + swalFire({ text: getMessage('wall.line.not.found') }) + closePopup(id) + return + } return () => { if (!isFixRef.current) { return diff --git a/src/hooks/surface/useSurfaceShapeBatch.js b/src/hooks/surface/useSurfaceShapeBatch.js index b26ee195..06474da0 100644 --- a/src/hooks/surface/useSurfaceShapeBatch.js +++ b/src/hooks/surface/useSurfaceShapeBatch.js @@ -755,6 +755,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { type: 'confirm', confirmFn: () => { canvas.clear() + delete canvas.outerLineFix if (backgroundImage) { fabric.Image.fromURL(`${backgroundImage.path}`, function (img) { From 7557f61130071b317b269795622e8a894d9277c7 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 31 Dec 2025 14:17:45 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=EB=B6=81=EB=A9=B4=20=EC=84=A4=EC=B9=98=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=20=EB=AA=A8=EB=93=88=EC=9D=80=20=EB=8B=A8?= =?UTF-8?q?=EB=8F=85=EC=9C=BC=EB=A1=9C=EB=A7=8C=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../step/type/PassivityCircuitAllocation.jsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx index 1ee73892..720845f5 100644 --- a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx +++ b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx @@ -122,6 +122,21 @@ export default function PassivityCircuitAllocation(props) { return } + // targetModule중 북면 설치 여부가 Y인 것과 N인 것이 혼합이면 안됨. + const targetModuleGroup = [...new Set(canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE && targetModules.includes(obj.id)) + .map((obj) => obj.moduleInfo.northModuleYn))] + + if (targetModuleGroup.length > 1) { + swalFire({ + text: getMessage('module.circuit.fix.not.same.roof.error'), + type: 'alert', + icon: 'warning', + }) + return + } + switch (pcsTpCd) { case 'INDFCS': { const originHaveThisPcsModules = canvas From fd8cbf726c13735e74129502b1014600bb1a351a Mon Sep 17 00:00:00 2001 From: ysCha Date: Wed, 31 Dec 2025 15:41:46 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=ED=95=A0=EB=8B=B9=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roofcover/useRoofAllocationSetting.js | 56 ++++++++++++++++--- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js index 4bb3d8ca..5e65cbcf 100644 --- a/src/hooks/roofcover/useRoofAllocationSetting.js +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -476,16 +476,54 @@ export function useRoofAllocationSetting(id) { const newEaveLines = roofEaveHelpLines.filter((line) => !existingEaveLineIds.has(line.id)) // Filter out lines from roofBase.lines that share any points with newEaveLines const linesToKeep = roofBase.lines.filter(roofLine => { - return !newEaveLines.some(eaveLine => { - // Check if any endpoint of roofLine matches any endpoint of eaveLine - return ( - // Check if any endpoint of roofLine matches any endpoint of eaveLine - (Math.abs(roofLine.x1 - eaveLine.x1) < 0.1 && Math.abs(roofLine.y1 - eaveLine.y1) < 0.1) || // p1 matches p1 - (Math.abs(roofLine.x2 - eaveLine.x2) < 0.1 && Math.abs(roofLine.y2 - eaveLine.y2) < 0.1) // p2 matches p2 - ); - }); - }); + const shouldRemove = newEaveLines.some(eaveLine => { + // 1. 기본적인 포인트 일치 확인 + const rX1 = roofLine.x1, rY1 = roofLine.y1, rX2 = roofLine.x2, rY2 = roofLine.y2; + const eX1 = eaveLine.x1, eY1 = eaveLine.y1, eX2 = eaveLine.x2, eY2 = eaveLine.y2; + const isP1Matched = (Math.abs(rX1 - eX1) < 0.1 && Math.abs(rY1 - eY1) < 0.1) || (Math.abs(rX1 - eX2) < 0.1 && Math.abs(rY1 - eY2) < 0.1); + const isP2Matched = (Math.abs(rX2 - eX1) < 0.1 && Math.abs(rY2 - eY1) < 0.1) || (Math.abs(rX2 - eX2) < 0.1 && Math.abs(rY2 - eY2) < 0.1); + + if (isP1Matched || isP2Matched) { + // 2. 일직선(평행)인지 확인 + const dx1 = rX2 - rX1; + const dy1 = rY2 - rY1; + const dx2 = eX2 - eX1; + const dy2 = eY2 - eY1; + const crossProduct = Math.abs(dx1 * dy2 - dy1 * dx2); + const mag1 = Math.sqrt(dx1 * dx1 + dy1 * dy1); + const mag2 = Math.sqrt(dx2 * dx2 + dy2 * dy2); + const isStraight = (mag1 * mag2) === 0 ? true : (crossProduct / (mag1 * mag2) < 0.01); + + if (isStraight) { + // 3. [핵심] 몸통이 포개지는지(Overlap) 확인 + // 한 선의 끝점이 다른 선의 "내부"에 들어와 있는지 체크 + const isPointInside = (x, y, x1, y1, x2, y2) => { + const dotProduct = (x - x1) * (x2 - x1) + (y - y1) * (y2 - y1); + if (dotProduct < 0.1) return false; // 시작점 바깥쪽 + const squaredLength = (x2 - x1) ** 2 + (y2 - y1) ** 2; + if (dotProduct > squaredLength - 0.1) return false; // 끝점 바깥쪽 + return true; // 선의 내부(몸통)에 있음 + }; + + // roofLine의 끝점 중 하나가 eaveLine의 몸통 안에 있거나, + // eaveLine의 끝점 중 하나가 roofLine의 몸통 안에 있으면 "포개짐"으로 판단 + const isOverlapping = + isPointInside(rX1, rY1, eX1, eY1, eX2, eY2) || + isPointInside(rX2, rY2, eX1, eY1, eX2, eY2) || + isPointInside(eX1, eY1, rX1, rY1, rX2, rY2) || + isPointInside(eX2, eY2, rX1, rY1, rX2, rY2); + + if (isOverlapping) { + console.log('Removing overlapping line:', roofLine); + return true; // 포개지는 경우에만 삭제 + } + } + } + return false; // 끝점만 닿아 있거나 직각인 경우는 살림 + }); + return !shouldRemove; + }); // Combine remaining lines with newEaveLines roofBase.lines = [...linesToKeep, ...newEaveLines]; } else { From a74789d8e8705f6a312e7f9c58be02ca0d07e01c Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Fri, 2 Jan 2026 14:37:08 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=ED=9A=8C=EC=A0=84=20=ED=9B=84=20=EC=9E=98?= =?UTF-8?q?=EB=A6=AC=EB=8A=94=20=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/surface/useSurfaceShapeBatch.js | 29 ++++++++++++++++------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/hooks/surface/useSurfaceShapeBatch.js b/src/hooks/surface/useSurfaceShapeBatch.js index 06474da0..d485b719 100644 --- a/src/hooks/surface/useSurfaceShapeBatch.js +++ b/src/hooks/surface/useSurfaceShapeBatch.js @@ -1,6 +1,6 @@ 'use client' -import { useRecoilValue, useResetRecoilState } from 'recoil' +import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil' import { canvasSettingState, canvasState, @@ -50,7 +50,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { const { changeCorridorDimensionText } = useText() const currentCanvasPlan = useRecoilValue(currentCanvasPlanState) const { fetchSettings } = useCanvasSetting(false) - const currentObject = useRecoilValue(currentObjectState) + const [currentObject, setCurrentObject] = useRecoilState(currentObjectState) const [popupId, setPopupId] = useState(uuidv4()) const applySurfaceShape = (surfaceRefs, selectedType, id) => { @@ -1525,6 +1525,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { // 개별 객체들을 다시 캔버스에 추가하고 처리 group.getObjects().forEach((obj) => { canvas.add(obj) + obj.dirty = true // 캐시 무효화 obj.setCoords() // currentObject인 경우 추가 처리 @@ -1535,6 +1536,8 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { // QPolygon 내부 구조 재구성 (선이 깨지는 문제 해결) if (obj.type === 'QPolygon' && obj.lines) { obj.initLines() + obj.dirty = true + obj.setCoords() } obj.set({ @@ -1545,6 +1548,8 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { // relatedObject인 경우에도 필요한 처리 if (obj.type === 'QPolygon' && obj.lines) { obj.initLines() + obj.dirty = true + obj.setCoords() } if (obj.type === 'group') { // 회전 후의 points를 groupPoints로 업데이트 @@ -1552,24 +1557,30 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { obj.recalculateGroupPoints() - obj._objects?.forEach((obj) => { - obj.initLines() - obj.fire('modified') + obj._objects?.forEach((innerObj) => { + innerObj.initLines() + innerObj.dirty = true + innerObj.setCoords() + innerObj.fire('modified') }) } } }) + + currentObject.dirty = true + currentObject.setCoords() currentObject.fire('modified') + currentObject.fire('polygonMoved') // 화살표와 선 다시 그리기 drawDirectionArrow(currentObject) setTimeout(() => { setPolygonLinesActualSize(currentObject) changeSurfaceLineType(currentObject) + currentObject.dirty = true + currentObject.setCoords() + canvas.requestRenderAll() + setCurrentObject(currentObject) }, 500) - - // currentObject를 다시 선택 상태로 설정 - canvas.setActiveObject(currentObject) - canvas.renderAll() } }