fix: 지붕형상 설정/수동설정 외벽선 표시·선택 상태 정리

[작업내용] :
- 지붕형상 저장 후 색칠된 외벽선 선택·드래그 차단, 변별로 설정 재진입 시 선택 복원
- 수동설정 저장 시에도 자동설정과 동일하게 외벽선을 타입별 색상으로 표시
- 두 모달을 진입 시 스냅샷 → 저장 시 커밋 / 저장 없이 닫으면 롤백 구조로 변경
  (팝업을 열었다 닫기만 해도 외벽선이 숨겨지고 지붕면이 삭제되던 문제 수정)
- 롤백 시 WALL 폴리곤은 원본 객체를 원래 z-순서에 재삽입
  (재생성 시 최상단에 올라가 보조선 클릭을 가로채던 문제 방지)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jaeyoung Lee 2026-06-10 13:22:44 +09:00
parent 54e6a31dcf
commit e17af8f4b4
2 changed files with 91 additions and 32 deletions

View File

@ -36,6 +36,7 @@ export function useRoofShapePassivitySetting(id) {
const [type, setType] = useState(TYPES.EAVES) const [type, setType] = useState(TYPES.EAVES)
const isFix = useRef(false) const isFix = useRef(false)
const initLines = useRef([]) const initLines = useRef([])
const removedWalls = useRef([])
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const { closePopup } = usePopup() const { closePopup } = usePopup()
@ -60,6 +61,7 @@ export function useRoofShapePassivitySetting(id) {
addCanvasMouseEventListener('mouse:down', mouseDown) addCanvasMouseEventListener('mouse:down', mouseDown)
const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL)
removedWalls.current = wallLines.map((wall) => ({ wall, index: canvas.getObjects().indexOf(wall) }))
canvas?.remove(...wallLines) canvas?.remove(...wallLines)
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
@ -198,10 +200,32 @@ export function useRoofShapePassivitySetting(id) {
} }
const handleLineToPolygon = () => { const handleLineToPolygon = () => {
const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
const exceptObjs = canvas.getObjects().filter((obj) => obj.name !== 'outerLine' && obj.parent?.name !== 'outerLine')
const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
if (!isFix.current) {
// 저장 없이 닫을 경우 진입 시점 스냅샷으로 롤백 — 캔버스를 진입 전 상태 그대로 되돌린다
lines.forEach((line, idx) => {
const init = initLines.current[idx]
if (!init) {
return
}
init.visible ? showLine(line) : hideLine(line)
line.set({ stroke: init.stroke, strokeWidth: init.strokeWidth, selectable: init.selectable })
line.attributes = init.attributes
})
// 진입 시 삭제했던 WALL 폴리곤 원본을 원래 z-순서 그대로 재삽입
removedWalls.current.forEach(({ wall, index }) => {
canvas.insertAt(wall, index)
})
removedWalls.current = []
canvas.renderAll()
return
}
const exceptObjs = canvas.getObjects().filter((obj) => obj.name !== 'outerLine' && obj.parent?.name !== 'outerLine')
let checkedAllSetting = true let checkedAllSetting = true
lines.forEach((line) => { lines.forEach((line) => {
@ -219,36 +243,27 @@ export function useRoofShapePassivitySetting(id) {
canvas.remove(obj) canvas.remove(obj)
}) })
// 저장 시에는 자동 설정과 동일하게 외벽선을 타입별 색상으로 표시
lines.forEach((line) => { lines.forEach((line) => {
hideLine(line) let stroke
if (line.attributes?.type === LINE_TYPE.WALLLINE.EAVES || line.attributes?.type === LINE_TYPE.WALLLINE.HIPANDGABLE) {
stroke = '#45CD7D'
} else if (line.attributes?.type === LINE_TYPE.WALLLINE.GABLE || line.attributes?.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
stroke = '#3FBAE6'
} else {
stroke = '#000000'
}
line.set({ stroke, strokeWidth: 4, selectable: false, visible: true })
}) })
let wall const wall = addPolygonByLines(lines, { name: POLYGON_TYPE.WALL, fill: 'transparent', stroke: 'black' })
if (isFix.current) {
wall = addPolygonByLines(lines, { name: POLYGON_TYPE.WALL, fill: 'transparent', stroke: 'black' })
} else {
// 그냥 닫을 경우 처리
wall = addPolygonByLines([...initLines.current], {
name: POLYGON_TYPE.WALL,
fill: 'transparent',
stroke: 'black',
})
lines.forEach((line, idx) => {
line.attributes = initLines.current[idx].attributes
})
}
wall.lines = [...lines] wall.lines = [...lines]
// 기존 그려진 지붕이 없다면 // 완료 한 경우에는 지붕까지 그려줌
addPitchTextsByOuterLines()
if (isFix.current) { const roof = drawRoofPolygon(wall)
// 완료 한 경우에는 지붕까지 그려줌 addLengthText(roof)
addPitchTextsByOuterLines() lines.forEach((line) => line.bringToFront())
const roof = drawRoofPolygon(wall)
addLengthText(roof)
}
canvas.renderAll() canvas.renderAll()
} }

View File

@ -52,6 +52,11 @@ export function useRoofShapeSetting(id) {
const isFixRef = useRef(false) const isFixRef = useRef(false)
// 저장 없이 닫을 때 롤백할 진입 시점 스냅샷
const initLinesRef = useRef([])
const removedWallsRef = useRef([])
const pitchTextLineIdsRef = useRef(new Set())
const pitchRef = useRef(null) const pitchRef = useRef(null)
const shedPitchRef = useRef(null) const shedPitchRef = useRef(null)
const jerkinHeadPitchRef = useRef(null) const jerkinHeadPitchRef = useRef(null)
@ -87,14 +92,46 @@ export function useRoofShapeSetting(id) {
closePopup(id) closePopup(id)
return return
} }
return () => {
if (!isFixRef.current) {
return
}
initLinesRef.current = outerLines.map((line) => ({ ...line }))
pitchTextLineIdsRef.current = new Set(
canvas
.getObjects()
.filter((obj) => obj.name === 'pitchText')
.map((obj) => obj.parentId),
)
return () => {
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
const pitchTexts = canvas.getObjects().filter((obj) => obj.name === 'pitchText') const pitchTexts = canvas.getObjects().filter((obj) => obj.name === 'pitchText')
if (!isFixRef.current) {
// 저장 없이 닫을 경우 진입 시점 스냅샷으로 롤백 — 캔버스를 진입 전 상태 그대로 되돌린다
canvas.remove(...pitchTexts)
outerLines.forEach((line, idx) => {
const init = initLinesRef.current[idx]
if (!init) {
return
}
init.visible ? showLine(line) : hideLine(line)
line.set({ stroke: init.stroke, strokeWidth: init.strokeWidth, selectable: init.selectable })
line.attributes = init.attributes
if (pitchTextLineIdsRef.current.has(line.id)) {
addPitchText(line)
}
})
// 변별로 설정 진입 시 삭제했던 WALL 폴리곤 원본을 원래 z-순서 그대로 재삽입
removedWallsRef.current.forEach(({ wall, index }) => {
canvas.insertAt(wall, index)
})
removedWallsRef.current = []
canvas.discardActiveObject()
canvas.renderAll()
return
}
canvas.remove(...pitchTexts) canvas.remove(...pitchTexts)
outerLines.forEach((line) => { outerLines.forEach((line) => {
let stroke, strokeWidth let stroke, strokeWidth
@ -113,6 +150,8 @@ export function useRoofShapeSetting(id) {
line.set({ line.set({
stroke, stroke,
strokeWidth, strokeWidth,
selectable: false,
visible: true,
}) })
addPitchText(line) addPitchText(line)
@ -144,10 +183,15 @@ export function useRoofShapeSetting(id) {
useEffect(() => { useEffect(() => {
if (shapeNum === 4) { if (shapeNum === 4) {
canvas?.remove(canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL)) const wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL)
if (wall && removedWallsRef.current.length === 0) {
removedWallsRef.current = [{ wall, index: canvas.getObjects().indexOf(wall) }]
}
canvas?.remove(wall)
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
outerLines.forEach((line) => { outerLines.forEach((line) => {
showLine(line) showLine(line)
line.set({ selectable: true })
line.bringToFront() line.bringToFront()
}) })