From cce18d47adcaca1a4674304fbf3276b5c3242a28 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Fri, 24 Apr 2026 16:14:58 +0900 Subject: [PATCH] =?UTF-8?q?=EB=8F=84=EB=A8=B8=20=EC=84=A4=EC=B9=98?= =?UTF-8?q?=EB=90=9C=20=EC=A7=80=EB=B6=95=EB=A9=B4=20=EC=9D=B4=EB=8F=99?= =?UTF-8?q?=EC=8B=9C=20=EC=9D=B4=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 | 5 ++++ src/hooks/useUndoRedo.js | 48 +++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index 189072db..353cb344 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -146,11 +146,13 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { this.setCoords() // 보조선 / 자식 객체들도 같이 이동시킨다 (ROOF 가 아닌 WALL 등에서도 동작하도록) + // 단, fabric.Group(도머 등)은 절대 위치 유지해야 하므로 제외 if ((dxModified !== 0 || dyModified !== 0) && this.canvas) { const directChildren = this.canvas.getObjects().filter((obj) => { if (obj === this) return false if (obj.parentId !== this.id) return false if (obj.name === 'lengthText') return false + if (obj.type === 'group') return false // 도머 등 Group 은 이동 대상에서 제외 return true }) const childIds = new Set(directChildren.map((c) => c.id).filter(Boolean)) @@ -181,6 +183,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { movedTextCount++ } }) + this.canvas.renderAll() // ROOF 의 경우 polygonMoved 가 같은 일을 다시 하므로, _preDragLeft 는 거기서 정리됨 @@ -277,6 +280,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { if (obj === this) return false if (obj.parentId !== this.id) return false if (obj.name === 'lengthText') return false // 이미 재생성됨 + if (obj.type === 'group') return false // 도머 등 Group 은 절대 위치 유지 return true }) @@ -318,6 +322,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { movedTextCount++ } }) + this.canvas.renderAll() } diff --git a/src/hooks/useUndoRedo.js b/src/hooks/useUndoRedo.js index cd1ef27b..35df6b2b 100644 --- a/src/hooks/useUndoRedo.js +++ b/src/hooks/useUndoRedo.js @@ -197,7 +197,29 @@ export function useUndoRedo() { }) // 부모가 없는 고아 lengthText, arrow, flowText 제거 - const parentIds = new Set(objects.filter((obj) => obj.id).map((obj) => obj.id)) + // fabric.Group 내부의 QPolygon/QLine 자식 id도 부모로 간주해야 + // 도머(Group) 내부 삼각형들의 lengthText가 사라지지 않음 + const parentIds = new Set() + objects.forEach((obj) => { + if (obj.id) parentIds.add(obj.id) + if (obj.type === 'group' && obj._objects) { + obj._objects.forEach((child) => { + if (child.id) parentIds.add(child.id) + // 그룹 내부 QPolygon의 lines(QLine) id도 포함 + if (child.lines && Array.isArray(child.lines)) { + child.lines.forEach((line) => { + if (line.id) parentIds.add(line.id) + }) + } + }) + } + // 일반 QPolygon의 lines id도 포함 (내부 보조선/치수선의 parentId) + if (obj.lines && Array.isArray(obj.lines)) { + obj.lines.forEach((line) => { + if (line.id) parentIds.add(line.id) + }) + } + }) const orphans = objects.filter( (obj) => (obj.name === 'lengthText' || obj.name === 'arrow' || obj.name === 'flowText') && obj.parentId && !parentIds.has(obj.parentId), ) @@ -275,7 +297,14 @@ export function useUndoRedo() { // loadFromJSON 후 QPolygon/QLine 의 lengthText 가 orphan 제거되었을 수 있으므로 재생성 // (initLines 로 새 QLine 이 만들어지면 기존 lengthText 의 parentId 가 안 맞음) - const qPolygons = objects.filter((obj) => obj.type === 'QPolygon' && obj.name !== 'arrow' && obj.name !== POLYGON_TYPE.MODULE && obj.name !== POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.name !== POLYGON_TYPE.OBJECT_SURFACE) + const shouldRebuildText = (poly) => + poly.type === 'QPolygon' && + poly.name !== 'arrow' && + poly.name !== POLYGON_TYPE.MODULE && + poly.name !== POLYGON_TYPE.MODULE_SETUP_SURFACE && + poly.name !== POLYGON_TYPE.OBJECT_SURFACE + + const qPolygons = objects.filter(shouldRebuildText) qPolygons.forEach((polygon) => { try { polygon.initLines() @@ -283,6 +312,21 @@ export function useUndoRedo() { } catch (e) { /* skip */ } }) + // Group 내부 QPolygon(도머 등)의 lengthText 도 동일하게 재생성 + objects + .filter((obj) => obj.type === 'group' && obj._objects) + .forEach((group) => { + group._objects.forEach((child) => { + if (shouldRebuildText(child)) { + try { + if (!child.canvas) child.canvas = canvas + child.initLines?.() + child.addLengthText?.() + } catch (e) { /* skip */ } + } + }) + }) + canvas.getObjects() .filter((obj) => obj.type === 'QLine') .forEach((line) => {