From 2aef32e03ceb93bae981c03f199f1cbf596b24af Mon Sep 17 00:00:00 2001 From: ysCha Date: Fri, 8 May 2026 17:48:37 +0900 Subject: [PATCH] =?UTF-8?q?[2204]=200-=EB=A9=B4=EC=A0=81=20=EA=B0=9C?= =?UTF-8?q?=EA=B5=AC/=EA=B7=B8=EB=A6=BC=EC=9E=90/=EB=8F=84=EB=A8=B8=20?= =?UTF-8?q?=EC=B0=A8=EB=8B=A8=20=E2=80=94=20SizeSetting=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EA=B0=80=EB=93=9C=20+=20=EC=86=90=EC=83=81=20plan?= =?UTF-8?q?=20=EB=A1=9C=EB=93=9C=20skip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../floor-plan/modal/object/SizeSetting.jsx | 11 ++++++++++ src/hooks/module/useModuleBasicSetting.js | 21 +++++++++++++++++++ src/hooks/object/useObjectBatch.js | 12 +++++++++++ 3 files changed, 44 insertions(+) diff --git a/src/components/floor-plan/modal/object/SizeSetting.jsx b/src/components/floor-plan/modal/object/SizeSetting.jsx index edd5b895..00fb8e34 100644 --- a/src/components/floor-plan/modal/object/SizeSetting.jsx +++ b/src/components/floor-plan/modal/object/SizeSetting.jsx @@ -11,6 +11,7 @@ import { useObjectBatch } from '@/hooks/object/useObjectBatch' import { BATCH_TYPE, POLYGON_TYPE } from '@/common/common' import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch' import { CalculatorInput } from '@/components/common/input/CalcInput' +import { useSwal } from '@/hooks/useSwal' export default function SizeSetting(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) @@ -20,6 +21,7 @@ export default function SizeSetting(props) { const { closePopup } = usePopup() const { resizeObjectBatch } = useObjectBatch({}) const { resizeSurfaceShapeBatch } = useSurfaceShapeBatch({}) + const { swalFire } = useSwal() const widthRef = useRef(null) const heightRef = useRef(null) const [width, setWidth] = useState(target?.width ? (target.width * 10).toFixed() : 0) @@ -46,6 +48,15 @@ export default function SizeSetting(props) { const width = widthRef.current.value const height = heightRef.current.value + // [OPENING-MIN-SIZE 2026-05-08] 0/빈값 입력 시 적용 차단. 0,0 으로 확정되면 4정점 동일 + // 0-dim 객체가 plan 에 저장되어 모듈배치 단계에서 turf throw → 화면 크래시. + if (target.name === BATCH_TYPE.OPENING || target.name === BATCH_TYPE.SHADOW) { + if (width === '' || height === '' || Number(width) <= 0 || Number(height) <= 0) { + swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' }) + return + } + } + if (target.name === BATCH_TYPE.OPENING || target.name === BATCH_TYPE.SHADOW) { resizeObjectBatch(settingTarget, target, width, height, id) } else if (target.name === POLYGON_TYPE.ROOF) { diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index ef477e67..e9a2aab5 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -327,6 +327,15 @@ export function useModuleBasicSetting(tabNum) { //도머등 오브젝트 객체가 있으면 아웃라인 낸다 batchObjects.forEach((obj) => { + // [OPENING-MIN-SIZE 2026-05-08] 기존 plan 에 저장된 0-면적 개구/그림자/도머 보호: + // 모든 정점이 동일 좌표면 offsetPolygon → cleanSelfIntersectingPolygon → turf.polygon throw 발생. + // 신규는 SizeSetting 가드로 막지만, 이미 저장된 데이터는 여기서 skip. + const _pts = obj.getCurrentPoints?.() || obj.points || [] + if (_pts.length >= 2 && _pts.every((p) => p.x === _pts[0].x && p.y === _pts[0].y)) { + console.warn('[OPENING-MIN-SIZE 2026-05-08] 0-dim batch object skipped (outline)', { id: obj.id, name: obj.name, parentId: obj.parentId }) + return + } + //도머일때 if (obj.name === BATCH_TYPE.TRIANGLE_DORMER || obj.name === BATCH_TYPE.PENTAGON_DORMER) { const groupPoints = obj.getCurrentPoints() @@ -960,6 +969,12 @@ export function useModuleBasicSetting(tabNum) { //도머 객체를 가져옴 if (batchObjects) { batchObjects.forEach((object) => { + // [OPENING-MIN-SIZE 2026-05-08] 0-면적 객체는 turf 변환 시 throw 위험 → skip + const _pts = object.getCurrentPoints?.() || object.points || [] + if (_pts.length >= 2 && _pts.every((p) => p.x === _pts[0].x && p.y === _pts[0].y)) { + console.warn('[OPENING-MIN-SIZE 2026-05-08] 0-dim batch object skipped (intersect)', { id: object.id, name: object.name }) + return + } let dormerTurfPolygon = polygonToTurfPolygon(object, true) const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인 //겹치면 안됨 @@ -3716,6 +3731,12 @@ export function useModuleBasicSetting(tabNum) { //도머 객체를 가져옴 if (batchObjects) { batchObjects.forEach((object) => { + // [OPENING-MIN-SIZE 2026-05-08] 0-면적 객체는 turf 변환 시 throw 위험 → skip + const _pts = object.getCurrentPoints?.() || object.points || [] + if (_pts.length >= 2 && _pts.every((p) => p.x === _pts[0].x && p.y === _pts[0].y)) { + console.warn('[OPENING-MIN-SIZE 2026-05-08] 0-dim batch object skipped (intersect)', { id: object.id, name: object.name }) + return + } let dormerTurfPolygon if (object.type === 'group') { diff --git a/src/hooks/object/useObjectBatch.js b/src/hooks/object/useObjectBatch.js index 2af636a5..cc71a3b8 100644 --- a/src/hooks/object/useObjectBatch.js +++ b/src/hooks/object/useObjectBatch.js @@ -287,6 +287,10 @@ export function useObjectBatch({ isHidden, setIsHidden }) { } isDown = false + // [OPENING-COLLAPSE-DIAG 2026-05-08] 치수입력 mouse:up 시점 width/height 0 검출 + if (!rect.width || !rect.height) { + console.warn('[OPENING-COLLAPSE-DIAG] dimension-input mouse:up rect collapsed:', { name: objName, w: rect.width, h: rect.height, sx: rect.scaleX, sy: rect.scaleY, left: rect.left, top: rect.top }) + } rect.set({ name: objName, parentId: selectedSurface.id, points: rectToPolygon(rect) }) rect.setCoords() initEvent() @@ -1430,6 +1434,10 @@ export function useObjectBatch({ isHidden, setIsHidden }) { addCanvasMouseEventListener('mouse:up', (e) => { //개구, 그림자 타입일 경우 폴리곤 타입 변경 if (BATCH_TYPE.OPENING === obj.name || BATCH_TYPE.SHADOW === obj.name) { + // [OPENING-COLLAPSE-DIAG 2026-05-08] 객체 이동 mouse:up 시점 width/height 0 검출 + if (!obj.width || !obj.height || !obj.scaleX || !obj.scaleY) { + console.warn('[OPENING-COLLAPSE-DIAG] move mouse:up obj collapsed:', { id: obj.id, name: obj.name, w: obj.width, h: obj.height, sx: obj.scaleX, sy: obj.scaleY, left: obj.left, top: obj.top }) + } obj.set({ points: rectToPolygon(obj), }) @@ -1532,6 +1540,10 @@ export function useObjectBatch({ isHidden, setIsHidden }) { addCanvasMouseEventListener('mouse:up', (e) => { //개구, 그림자 타입일 경우 폴리곤 타입 변경 if (BATCH_TYPE.OPENING == obj.name || BATCH_TYPE.SHADOW == obj.name) { + // [OPENING-COLLAPSE-DIAG 2026-05-08] 복제 mouse:up 시점 width/height 0 검출 + if (!clonedObj.width || !clonedObj.height || !clonedObj.scaleX || !clonedObj.scaleY) { + console.warn('[OPENING-COLLAPSE-DIAG] copy mouse:up clonedObj collapsed:', { name: obj.name, w: clonedObj.width, h: clonedObj.height, sx: clonedObj.scaleX, sy: clonedObj.scaleY, left: clonedObj.left, top: clonedObj.top }) + } clonedObj.set({ points: rectToPolygon(clonedObj), })