From bbd3d1ec4b75c0bced2ed858a36cad218f234e67 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Mon, 29 Dec 2025 14:51:55 +0900 Subject: [PATCH] =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EB=B3=B5=EC=82=AC,=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0,=20=EC=B6=94=EA=B0=80=20=ED=9B=84=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=20=EC=95=88=EB=90=98=EB=8A=94=20=ED=98=84?= =?UTF-8?q?=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/components/fabric/QPolygon.js | 28 +++---- src/hooks/module/useModule.js | 132 ++++++++++++++++++++++++++---- 2 files changed, 126 insertions(+), 34 deletions(-) diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index c87d161f..8d173bc6 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -845,37 +845,33 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { // 먼저 좌표 업데이트 this.setCoords() - // 캔버스 줌과 viewport transform 고려한 좌표 변환 - let localPoint = point + // viewport transform만 역변환 (캔버스 줌/팬 보정) + // 결과는 WORLD 좌표 (캔버스 좌표계) + let canvasPoint = point if (this.canvas) { const vpt = this.canvas.viewportTransform if (vpt) { - // viewport transform 역변환 const inverted = fabric.util.invertTransform(vpt) - localPoint = fabric.util.transformPoint(point, inverted) + canvasPoint = fabric.util.transformPoint(point, inverted) } } - // 오브젝트의 transform matrix를 고려한 좌표 변환 - const matrix = this.calcTransformMatrix() - const invertedMatrix = fabric.util.invertTransform(matrix) - const transformedPoint = fabric.util.transformPoint(localPoint, invertedMatrix) - - // pathOffset을 고려한 최종 좌표 계산 - const pathOffset = this.get('pathOffset') - const finalPoint = { - x: Number((transformedPoint.x + pathOffset.x).toFixed(this.toFixed)), - y: Number((transformedPoint.y + pathOffset.y).toFixed(this.toFixed)), + // canvasPoint는 WORLD 좌표 + // inPolygonImproved에서 getCurrentPoints()도 WORLD 좌표를 반환 + // 따라서 좌표 시스템이 일치함 + const checkPoint = { + x: Number(canvasPoint.x.toFixed(this.toFixed)), + y: Number(canvasPoint.y.toFixed(this.toFixed)), } if (this.name === POLYGON_TYPE.ROOF && this.isFixed) { - const isInside = this.inPolygonImproved(finalPoint) + const isInside = this.inPolygonImproved(checkPoint) if (!this.selectable) { this.set('selectable', isInside) } return isInside } else { - return this.inPolygonImproved(finalPoint) + return this.inPolygonImproved(checkPoint) } }, diff --git a/src/hooks/module/useModule.js b/src/hooks/module/useModule.js index fa51ca75..45b3b50f 100644 --- a/src/hooks/module/useModule.js +++ b/src/hooks/module/useModule.js @@ -84,6 +84,7 @@ export function useModule() { canvas.renderAll() }) + const surfaceId = selectedModules[0].surfaceId if (isWarning) { swalFire({ title: getMessage('can.not.move.module'), @@ -96,11 +97,15 @@ export function useModule() { top: module.originCoords.top, fill: module.originCoords.fill, }) + module.dirty = true module.setCoords() }) - canvas.renderAll() + canvas.requestRenderAll() + recalculateAllModulesCoords(surfaceId) }, }) + } else { + recalculateAllModulesCoords(surfaceId) } } } @@ -157,6 +162,7 @@ export function useModule() { activeModule.set({ strokeWidth: 3 }) canvas.renderAll() + const surfaceId = activeModule.surfaceId if (isWarning) { swalFire({ title: getMessage('can.not.move.module'), @@ -165,11 +171,15 @@ export function useModule() { confirmFn: () => { modules.forEach((module) => { module.set({ top: module.originPos.top, left: module.originPos.left, fill: module.originPos.fill }) + module.dirty = true module.setCoords() }) - canvas.renderAll() + canvas.requestRenderAll() + recalculateAllModulesCoords(surfaceId) }, }) + } else { + recalculateAllModulesCoords(surfaceId) } } @@ -202,6 +212,7 @@ export function useModule() { }) canvas.renderAll() + const surfaceId = surface.id if (isWarning) { swalFire({ title: getMessage('can.not.move.module'), @@ -210,11 +221,15 @@ export function useModule() { confirmFn: () => { modules.forEach((module) => { module.set({ top: module.originPos.top, left: module.originPos.left, fill: module.originPos.fill }) + module.dirty = true module.setCoords() }) - canvas.renderAll() + canvas.requestRenderAll() + recalculateAllModulesCoords(surfaceId) }, }) + } else { + recalculateAllModulesCoords(surfaceId) } }) } @@ -269,6 +284,7 @@ export function useModule() { canvas.renderAll() }) + const surfaceId = surface.id if (isWarning) { swalFire({ title: getMessage('can.not.copy.module'), @@ -276,11 +292,13 @@ export function useModule() { type: 'alert', confirmFn: () => { canvas.remove(...copyModules) - canvas.renderAll() + canvas.requestRenderAll() + recalculateAllModulesCoords(surfaceId) }, }) } else { surface.set({ modules: [...surface.modules, ...copyModules] }) + recalculateAllModulesCoords(surfaceId) } }) @@ -333,6 +351,7 @@ export function useModule() { } }) + const surfaceId = modules[0].surfaceId if (isWarning) { swalFire({ title: getMessage('can.not.copy.module'), @@ -340,11 +359,13 @@ export function useModule() { type: 'alert', confirmFn: () => { canvas.remove(...copyModules) - canvas.renderAll() + canvas.requestRenderAll() + recalculateAllModulesCoords(surfaceId) }, }) } else { moduleSetupSurface.set({ modules: [...moduleSetupSurface.modules, ...copyModules] }) + recalculateAllModulesCoords(surfaceId) } setModuleStatisticsData() } @@ -426,6 +447,7 @@ export function useModule() { }) activeModule.set({ strokeWidth: 3 }) + const surfaceId = activeModule.surfaceId if (isWarning) { swalFire({ title: getMessage('can.not.copy.module'), @@ -433,12 +455,14 @@ export function useModule() { type: 'alert', confirmFn: () => { canvas.remove(...copyModules) - canvas.renderAll() + canvas.requestRenderAll() + recalculateAllModulesCoords(surfaceId) }, }) } else { moduleSetupSurface.set({ modules: [...moduleSetupSurface.modules, ...copyModules] }) setModuleStatisticsData() + recalculateAllModulesCoords(surfaceId) } } @@ -477,6 +501,7 @@ export function useModule() { } if (width === -1) width = module.left - activeModule.left module.set({ left: module.left - width }) + module.dirty = true module.setCoords() canvas.renderAll() if (isOverlapOtherModules(module, leftModules) || isOverlapObjects(module, objects) || isOutsideSurface(module, moduleSetupSurface)) { @@ -484,7 +509,7 @@ export function useModule() { isWarning = true } }) - canvas.renderAll() + canvas.requestRenderAll() targetModules = rightModules } else if (type === MODULE_REMOVE_TYPE.RIGHT) { leftModules.forEach((module) => { @@ -495,6 +520,7 @@ export function useModule() { } if (width === -1) width = activeModule.left - module.left module.set({ left: module.left + width }) + module.dirty = true module.setCoords() canvas.renderAll() if (isOverlapOtherModules(module, rightModules) || isOverlapObjects(module, objects) || isOutsideSurface(module, moduleSetupSurface)) { @@ -502,7 +528,7 @@ export function useModule() { isWarning = true } }) - canvas.renderAll() + canvas.requestRenderAll() targetModules = leftModules } else if (type === MODULE_REMOVE_TYPE.HORIZONTAL_SIDE) { const sideModules = [...leftModules, ...rightModules] @@ -514,6 +540,7 @@ export function useModule() { } if (width === -1) width = activeModule.left - module.left module.set({ left: module.left + width / 2 }) + module.dirty = true module.setCoords() canvas.renderAll() }) @@ -526,6 +553,7 @@ export function useModule() { } if (width === -1) width = module.left - activeModule.left module.set({ left: module.left - width / 2 }) + module.dirty = true module.setCoords() canvas.renderAll() }) @@ -547,6 +575,7 @@ export function useModule() { targetModules = sideModules } canvas.renderAll() + const surfaceId = activeModule.surfaceId if (isWarning) { swalFire({ title: getMessage('can.not.remove.module'), @@ -557,17 +586,24 @@ export function useModule() { canvas.add(...columnModules) targetModules.forEach((module) => { module.set({ top: module.originPos.top, left: module.originPos.left, fill: module.originPos.fill }) + module.dirty = true module.setCoords() }) - canvas.renderAll() + columnModules.forEach((module) => { + module.dirty = true + module.setCoords() + }) + canvas.requestRenderAll() + recalculateAllModulesCoords(surfaceId) }, }) } else { moduleSetupSurface.modules = moduleSetupSurface.modules.filter( (module) => !columnModules.map((copyModule) => copyModule.id).includes(module.id), ) + setModuleStatisticsData() + recalculateAllModulesCoords(surfaceId) } - setModuleStatisticsData() } const moduleRowRemove = (type) => { @@ -599,6 +635,7 @@ export function useModule() { } if (height === -1) height = module.top - activeModule.top module.set({ top: module.top - height }) + module.dirty = true module.setCoords() canvas.renderAll() if (isOverlapOtherModules(module, topModules) || isOverlapObjects(module, objects) || isOutsideSurface(module, moduleSetupSurface)) { @@ -606,7 +643,7 @@ export function useModule() { module.set({ fill: 'red' }) } }) - canvas.renderAll() + canvas.requestRenderAll() targetModules = bottomModules } else if (type === MODULE_REMOVE_TYPE.BOTTOM) { topModules.forEach((module) => { @@ -617,6 +654,7 @@ export function useModule() { } if (height === -1) height = activeModule.top - module.top module.set({ top: module.top + activeModule.height }) + module.dirty = true module.setCoords() canvas.renderAll() if (isOverlapOtherModules(module, bottomModules) || isOverlapObjects(module, objects) || isOutsideSurface(module, moduleSetupSurface)) { @@ -624,6 +662,7 @@ export function useModule() { module.set({ fill: 'red' }) } }) + canvas.requestRenderAll() targetModules = topModules } else if (type === MODULE_REMOVE_TYPE.VERTICAL_SIDE) { topModules.forEach((module) => { @@ -635,6 +674,7 @@ export function useModule() { // if (height === -1) height = activeModule.top - module.top if (height === -1) height = activeModule.height module.set({ top: module.top + height / 2 }) + module.dirty = true module.setCoords() }) @@ -647,10 +687,11 @@ export function useModule() { // if (height === -1) height = module.top - activeModule.top if (height === -1) height = activeModule.height module.set({ top: module.top - height / 2 }) + module.dirty = true module.setCoords() }) - canvas.renderAll() + canvas.requestRenderAll() const sideModules = [...topModules, ...bottomModules] sideModules.forEach((module) => { if ( @@ -668,6 +709,7 @@ export function useModule() { targetModules = sideModules } canvas.renderAll() + const surfaceId = activeModule.surfaceId if (isWarning && type !== MODULE_REMOVE_TYPE.NONE) { targetModules.forEach((rect) => rect.set({ fill: 'red' })) swalFire({ @@ -679,13 +721,20 @@ export function useModule() { canvas.add(...rowModules) targetModules.forEach((module) => { module.set({ top: module.originPos.top, left: module.originPos.left, fill: module.originPos.fill }) + module.dirty = true module.setCoords() }) - canvas.renderAll() + rowModules.forEach((module) => { + module.dirty = true + module.setCoords() + }) + canvas.requestRenderAll() + recalculateAllModulesCoords(surfaceId) }, }) } setModuleStatisticsData() + recalculateAllModulesCoords(surfaceId) } const moduleColumnInsert = (type) => { @@ -756,6 +805,7 @@ export function useModule() { module.setCoords() }) canvas.renderAll() + const surfaceId = activeModule.surfaceId if (isWarning) { swalFire({ title: getMessage('can.not.insert.module'), @@ -764,16 +814,19 @@ export function useModule() { confirmFn: () => { targetModules.forEach((module) => { module.set({ top: module.originPos.top, left: module.originPos.left, fill: module.originPos.fill }) + module.dirty = true module.setCoords() }) canvas.remove(...copyModules) - canvas.renderAll() + canvas.requestRenderAll() + recalculateAllModulesCoords(surfaceId) }, }) } else { moduleSetupSurface.modules = [...moduleSetupSurface.modules, ...copyModules] + setModuleStatisticsData() + recalculateAllModulesCoords(surfaceId) } - setModuleStatisticsData() } const isFixedModule = () => { @@ -863,6 +916,7 @@ export function useModule() { }) canvas.renderAll() + const surfaceId = activeModule.surfaceId if (isWarning) { swalFire({ title: getMessage('can.not.insert.module'), @@ -871,16 +925,19 @@ export function useModule() { confirmFn: () => { targetModules.forEach((module) => { module.set({ top: module.originPos.top, left: module.originPos.left, fill: module.originPos.fill }) + module.dirty = true module.setCoords() }) canvas.remove(...copyModules) - canvas.renderAll() + canvas.requestRenderAll() + recalculateAllModulesCoords(surfaceId) }, }) } else { moduleSetupSurface.modules = [...moduleSetupSurface.modules, ...copyModules] + setModuleStatisticsData() + recalculateAllModulesCoords(surfaceId) } - setModuleStatisticsData() } const alignModule = (type, surfaceArray) => { @@ -924,6 +981,7 @@ export function useModule() { } }) canvas.renderAll() + const surfaceId = surface.id if (isWarning) { swalFire({ title: getMessage('can.not.align.module'), @@ -932,11 +990,15 @@ export function useModule() { confirmFn: () => { modules.forEach((module) => { module.set({ top: module.originPos.top, left: module.originPos.left, fill: module.originPos.fill }) + module.dirty = true module.setCoords() }) - canvas.renderAll() + canvas.requestRenderAll() + recalculateAllModulesCoords(surfaceId) }, }) + } else { + recalculateAllModulesCoords(surfaceId) } }) } @@ -964,6 +1026,7 @@ export function useModule() { canvas.remove(activeModule) canvas.renderAll() setModuleStatisticsData() + recalculateAllModulesCoords(activeModule.surfaceId) } const moduleRoofRemove = (surfaceArray) => { @@ -1049,6 +1112,38 @@ export function useModule() { removeTrestleMaterials() } + /** + * 모든 모듈의 좌표를 재계산 + * 열/단 삭제, 복사, 추가 후 호출하여 선택 영역(bounding box)을 업데이트 + * @param {string} surfaceId - 특정 surface의 모듈만 재계산 (선택적) + */ + const recalculateAllModulesCoords = (surfaceId = null) => { + if (!canvas) return + + const modules = canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE) + .filter((obj) => (surfaceId ? obj.surfaceId === surfaceId : true)) + + // 모듈의 캐시를 초기화하고 좌표 재계산 + modules.forEach((module) => { + // Fabric.js 내부 캐시 초기화 + delete module.oCoords + delete module.aCoords + delete module.lineCoords + delete module.__corner + delete module.matrixCache + delete module.ownMatrixCache + + // dirty 플래그 설정 및 좌표 재계산 + module.dirty = true + module.setCoords() + }) + + // 렌더링 + canvas.renderAll() + } + return { moduleMove, moduleMultiMove, @@ -1063,5 +1158,6 @@ export function useModule() { modulesRemove, moduleRoofRemove, alignModule, + recalculateAllModulesCoords, } }