dev #931

Merged
ysCha merged 95 commits from dev into prd-deploy 2026-06-23 17:47:25 +09:00
3 changed files with 60 additions and 13 deletions
Showing only changes of commit 0e9cf938e9 - Show all commits

View File

@ -265,6 +265,56 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
}
})
// [WALL→ROOF 동기 이동]
// 지붕형상 설정 후 wall 과 roof 는 별개의 폴리곤이고, hip/ridge/baseLine 같은 보조선은
// 모두 parentId=roof.id 로 묶여 있어 wall 의 directChildren 에 포함되지 않는다.
// wall 만 단독 이동시키면 roof+보조선이 옛 위치에 남아 시각적으로 어긋나므로
// wall 이동 시 연결된 roof 를 찾아 polygonMoved 로 같은 delta 만큼 이동시킨다.
// (polygonMoved 가 자체적으로 직속 자식 평행이동까지 처리하므로 별도 순회 불필요)
if (this.name === POLYGON_TYPE.WALL) {
const linkedRoof = this.canvas.getObjects().find(
(obj) => obj.name === POLYGON_TYPE.ROOF && obj.wall === this,
)
if (linkedRoof) {
linkedRoof._preDragLeft = linkedRoof.left
linkedRoof._preDragTop = linkedRoof.top
linkedRoof.set({
left: (linkedRoof.left ?? 0) + dxModified,
top: (linkedRoof.top ?? 0) + dyModified,
})
linkedRoof.fire('polygonMoved')
// polygonMoved 가 ROOF 분기에서 _preDragLeft 를 정리하지 않으므로 여기서 정리
linkedRoof._preDragLeft = null
linkedRoof._preDragTop = null
// roof 의 attributes.roofId 매칭 객체(예: 일부 baseLine, 텍스트류)는
// roof.id 와 다른 식별자로 묶여 polygonMoved 의 parentId === roof.id 필터에
// 잡히지 않을 수 있다. 누락 방지를 위해 추가 평행이동.
const extraKids = this.canvas.getObjects().filter((obj) => {
if (obj === linkedRoof) return false
if (obj.parentId === linkedRoof.id) return false // 이미 polygonMoved 가 처리
return obj.attributes?.roofId === linkedRoof.id
})
extraKids.forEach((obj) => {
const next = {}
if (obj.left != null) next.left = obj.left + dxModified
if (obj.top != null) next.top = obj.top + dyModified
if (obj.x1 != null) next.x1 = obj.x1 + dxModified
if (obj.y1 != null) next.y1 = obj.y1 + dyModified
if (obj.x2 != null) next.x2 = obj.x2 + dxModified
if (obj.y2 != null) next.y2 = obj.y2 + dyModified
obj.set(next)
if (obj.startPoint) obj.startPoint = { x: obj.startPoint.x + dxModified, y: obj.startPoint.y + dyModified }
if (obj.endPoint) obj.endPoint = { x: obj.endPoint.x + dxModified, y: obj.endPoint.y + dyModified }
if (obj.minX != null) obj.minX += dxModified
if (obj.maxX != null) obj.maxX += dxModified
if (obj.minY != null) obj.minY += dyModified
if (obj.maxY != null) obj.maxY += dyModified
obj.setCoords?.()
})
}
}
this.canvas.renderAll()
// ROOF 의 경우 polygonMoved 가 같은 일을 다시 하므로, _preDragLeft 는 거기서 정리됨

View File

@ -42,7 +42,7 @@ export function useRoofShapePassivitySetting(id) {
const [isLoading, setIsLoading] = useState(false)
const { closePopup } = usePopup()
const { saveSnapshot } = useUndoRedo()
const { saveSnapshot, setDrawing } = useUndoRedo()
const buttons = [
{ id: 1, name: getMessage('eaves'), type: TYPES.EAVES },
{ id: 2, name: getMessage('gable'), type: TYPES.GABLE },
@ -56,7 +56,13 @@ export function useRoofShapePassivitySetting(id) {
closePopup(id)
//return
}
// 수동설정 popup 진행 중에는 undo/redo/snapshot 차단
setDrawing(true)
setIsLoading(true)
return () => {
// popup 닫힐 때 (handleSave 후 closePopup, 또는 사용자가 그냥 닫기) 잠금 해제
setDrawing(false)
}
}, [])
useEffect(() => {

View File

@ -384,10 +384,7 @@ export function useUndoRedo() {
const saveSnapshot = useCallback((atomOverrides = null, { force = false } = {}) => {
if (!canvas || _isRestoring) return
// 지붕형상 수동설정 메뉴에서는 스냅샷을 쌓지 않는다 (force 시 예외)
if (!force && currentMenu === MENU.ROOF_COVERING.ROOF_SHAPE_PASSIVITY_SETTINGS) return
// 외벽선/배치면 등 그리기 진행 중에는 스냅샷을 쌓지 않는다 (force 시 예외)
// 외벽선/배치면/지붕형상 수동설정 등 그리기 진행 중에는 스냅샷을 쌓지 않는다 (force 시 예외)
if (!force && (_isDrawing || isDrawing)) return
// snapshot 전에 mouseLine 제거
@ -428,10 +425,7 @@ export function useUndoRedo() {
}, [setPopup])
const undo = useCallback(() => {
// 지붕형상 수동설정 중에는 undo 차단
if (currentMenu === MENU.ROOF_COVERING.ROOF_SHAPE_PASSIVITY_SETTINGS) return
// 외벽선/배치면 등 그리기 진행 중에는 undo 차단
// 외벽선/배치면/지붕형상 수동설정 등 그리기 진행 중에는 undo 차단
if (_isDrawing || isDrawing) return
const currentUndoStack = undoStackRef.current
@ -460,10 +454,7 @@ export function useUndoRedo() {
}, [canvas, currentMenu, isDrawing, getCanvasSnapshot, getAtomSnapshot, restoreAtomSnapshot, rebuildCanvasAtoms, closeAllPopups])
const redo = useCallback(() => {
// 지붕형상 수동설정 중에는 redo 차단
if (currentMenu === MENU.ROOF_COVERING.ROOF_SHAPE_PASSIVITY_SETTINGS) return
// 외벽선/배치면 등 그리기 진행 중에는 redo 차단
// 외벽선/배치면/지붕형상 수동설정 등 그리기 진행 중에는 redo 차단
if (_isDrawing || isDrawing) return
const currentRedoStack = redoStackRef.current