dev #931

Merged
ysCha merged 95 commits from dev into prd-deploy 2026-06-23 17:47:25 +09:00
5 changed files with 66 additions and 37 deletions
Showing only changes of commit 4d8fc60cf1 - Show all commits

View File

@ -619,7 +619,8 @@ export function useCommonUtils() {
}
const buttonAct = dormerName == BATCH_TYPE.TRIANGLE_DORMER ? 3 : 4
applyDormers(dormerParams, buttonAct)
// [undo 중복 fix] copyObject 에서 이미 saveSnapshot 을 호출했으므로 applyDormers 의 스냅샷은 건너뛴다.
applyDormers(dormerParams, buttonAct, true)
} else if (obj.name === 'roof' && obj.type === 'QPolygon') {
// roof(QPolygon) 객체는 순환 참조(lines[].parent -> polygon)로 인해
// fabric.clone() 사용 시 Maximum call stack size exceeded 에러 발생

View File

@ -313,7 +313,7 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
* @param {*} surfaceShapePolygons
* @returns
*/
const applyDormers = ({ height, width, pitch, offsetRef, offsetWidthRef, directionRef }, buttonAct) => {
const applyDormers = ({ height, width, pitch, offsetRef, offsetWidthRef, directionRef }, buttonAct, skipSnapshot = false) => {
const dormerName = buttonAct === 3 ? BATCH_TYPE.TRIANGLE_DORMER : BATCH_TYPE.PENTAGON_DORMER
const dormerTempName = buttonAct === 3 ? BATCH_TYPE.TRIANGLE_DORMER_TEMP : BATCH_TYPE.PENTAGON_DORMER_TEMP
@ -331,7 +331,9 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
return
}
saveSnapshot()
// [undo 중복 fix] 도머 복사(commonCopyObject)는 copyObject 에서 이미 saveSnapshot 을 호출한 뒤
// applyDormers 를 재사용하므로, 복사 경로에서는 skipSnapshot=true 로 중복 스냅샷을 막는다.
if (!skipSnapshot) saveSnapshot()
let theta = 0
@ -1602,7 +1604,6 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
}
const dormerOffset = (arrow1, arrow2, length1, length2) => {
saveSnapshot()
length1 = parseInt(length1) / 10
length2 = parseInt(length2) / 10
@ -1631,22 +1632,17 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
}
}
// [좌표 검증 fix] moveObjectBatch 와 동일하게 setCoords()+getCurrentPoints() 로 실제 이동된
// 월드좌표를 검사한다. 과거처럼 groupPoints+delta 로 검사하면 2배 이동된 좌표가 되어
// 도머가 지붕 내부에 있어도 within=false → '지붕내 설치' alert 오발생.
dormer.setCoords()
const parentSurface = canvas?.getObjects().filter((item) => item.name === POLYGON_TYPE.ROOF && item.id === dormer.parentId)[0]
const turfSurface = pointsToTurfPolygon(parentSurface.getCurrentPoints())
let turfDormer = pointsToTurfPolygon(rectToPolygon(dormer))
let currentPoints = []
if (dormer.name === BATCH_TYPE.TRIANGLE_DORMER || dormer.name === BATCH_TYPE.PENTAGON_DORMER) {
const calcLeft = dormer.left - left
const calcTop = dormer.top - top
currentPoints = dormer.groupPoints.map((item) => {
return {
x: item.x + calcLeft,
y: item.y + calcTop,
}
})
currentPoints = dormer.getCurrentPoints()
turfDormer = pointsToTurfPolygon(currentPoints)
}
@ -1659,10 +1655,15 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
//위치 원복`
dormer.left = left
dormer.top = top
dormer.setCoords()
return
}
// [undo 중복 fix] 검증 통과 후에만 스냅샷을 저장한다. 검증 실패(원복) 시에도 스냅샷이
// 쌓이면 도머 작업 undo 횟수가 불필요하게 늘어난다.
saveSnapshot()
if (dormer.type === 'group') {
dormer.set({ groupPoints: currentPoints })
const newDormer = reGroupObject(dormer)

View File

@ -185,6 +185,13 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) {
name: 'auxiliaryLine',
})
// [복사본 roofId 유실 fix] addLine 이 내부에서 attributes 를 {} 로 초기화하므로,
// 원본 attributes(roofId 등)를 복사해 복원한다. 미복원 시 복사본의 roofId 가 없어
// 우클릭 '보조선 전체삭제' 등 roofId 기반 처리가 누락된다.
if (newLine) {
newLine.attributes = { ...object.attributes }
}
// roofs의 innerLines에서 object와 같은 것을 찾아서 newLine으로 변경
const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
roofs.forEach((roof) => {

View File

@ -156,23 +156,7 @@ export function useMovementSetting(id) {
canvas.remove(CONFIRM_LINE_REF.current)
CONFIRM_LINE_REF.current = null
}
const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
roofs.forEach((roof) => {
roof.set({ stroke: '#1083E3' })
roof.innerLines.forEach((line) => line.set({ selectable: true, strokeWidth: 2, stroke: '#1083E3' }))
})
const walls = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL)
walls.forEach((wall) => {
if (wall.baseLines.length === 0) {
wall.baseLines = canvas.getObjects().filter((obj) => obj.name === 'baseLine' && obj.attributes.wallId === wall.id)
}
wall.baseLines.forEach((baseLine) => {
baseLine.set({ selectable: false, visible: false })
})
})
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
outerLines.forEach((line) => line.set({ visible: true }))
canvas.renderAll()
restoreLineStyles()
initEvent()
}
}, [])
@ -230,6 +214,28 @@ export function useMovementSetting(id) {
canvas.renderAll()
}, [currentObject])
/** / () .
* saveSnapshot 직전과 팝업 cleanup 양쪽에서 호출해, undo 강조색이 스냅샷에 박혀 복원되지 않도록 한다. */
const restoreLineStyles = () => {
const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
roofs.forEach((roof) => {
roof.set({ stroke: '#1083E3' })
roof.innerLines.forEach((line) => line.set({ selectable: true, strokeWidth: 2, stroke: '#1083E3' }))
})
const walls = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL)
walls.forEach((wall) => {
if (wall.baseLines.length === 0) {
wall.baseLines = canvas.getObjects().filter((obj) => obj.name === 'baseLine' && obj.attributes.wallId === wall.id)
}
wall.baseLines.forEach((baseLine) => {
baseLine.set({ selectable: false, visible: false })
})
})
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
outerLines.forEach((line) => line.set({ visible: true }))
canvas.renderAll()
}
const clearRef = () => {
if (type === TYPE.FLOW_LINE) {
// 안전한 ref 접근
@ -534,6 +540,10 @@ export function useMovementSetting(id) {
canvas.renderAll()
}
// [undo 색상 fix] 스냅샷은 동선이동/형올림내림 적용 직전 상태를 저장한다.
// 팝업 오픈 시 입힌 강조색이 스냅샷에 박히면 undo 시 강조색이 복원되므로,
// 스냅샷 저장 전에 평상 선 스타일로 되돌린다.
restoreLineStyles()
saveSnapshot()
const target = selectedObject.current !== null ? selectedObject.current : CONFIRM_LINE_REF.current?.target
if (!target) return

View File

@ -469,15 +469,25 @@ export function useContextMenu() {
innerLines.length = 0
}
// 확정되지 않은 보조선
const notFixedAuxiliaryLines = canvas.getObjects().filter((obj) => obj.name === 'auxiliaryLine' && !obj.isAuxiliaryFixed)
notFixedAuxiliaryLines.forEach((line) => {
// [전체삭제 누락 fix] 기존에는 미확정 보조선(!isAuxiliaryFixed)만 삭제해,
// 복사본/미확정 보조선을 우클릭하면 currentObject.roofId 가 위 innerLines 와 어긋나
// 확정된 보조선이 누락되었다. 확정 여부와 무관하게 모든 보조선을 삭제한다.
const auxiliaryLines = canvas.getObjects().filter((obj) => obj.name === 'auxiliaryLine')
auxiliaryLines.forEach((line) => {
canvas.remove(line)
})
//동이동현이동
const eaveHelpLines = canvas.getObjects().filter((obj) => obj.name === 'eaveHelpLine' && obj.parentId === currentObject.attributes.roofId)
// 지붕 innerLines 에 남은 보조선/eaveHelpLine 참조 정리 (stale 부활 방지, 용마루 hip·ridge 는 보존)
canvas.getObjects().forEach((obj) => {
if (Array.isArray(obj.innerLines) && obj.innerLines.length > 0) {
obj.innerLines = obj.innerLines.filter(
(line) => line.name !== 'auxiliaryLine' && line.name !== 'eaveHelpLine' && line.lineName !== 'eaveHelpLine',
)
}
})
//동이동현이동 - 모든 eaveHelpLine 삭제 (roofId 무관, name/lineName 두 식별자 모두 검사)
const eaveHelpLines = canvas.getObjects().filter((obj) => obj.name === 'eaveHelpLine' || obj.lineName === 'eaveHelpLine')
eaveHelpLines.forEach((line) => {
canvas.remove(line)
})